From c469ce9449025cd79199c43464899866050e7d82 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 13 Dec 2021 11:24:37 +0100
Subject: [PATCH 001/475] Implement check for minimum version of pcan library
---
can/interfaces/pcan/pcan.py | 19 +++++++++++++++++++
setup.py | 1 +
2 files changed, 20 insertions(+)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 959874a64..b1fef17d1 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -9,6 +9,8 @@
from typing import Optional
+from packaging import version
+
from ...message import Message
from ...bus import BusABC, BusState
from ...util import len2dlc, dlc2len
@@ -19,6 +21,7 @@
PCAN_BITRATES,
PCAN_FD_PARAMETER_LIST,
PCAN_CHANNEL_NAMES,
+ PCAN_NONEBUS,
PCAN_BAUD_500K,
PCAN_TYPE_ISA,
PCANBasic,
@@ -26,6 +29,7 @@
PCAN_ALLOW_ERROR_FRAMES,
PCAN_PARAMETER_ON,
PCAN_RECEIVE_EVENT,
+ PCAN_API_VERSION,
PCAN_DEVICE_NUMBER,
PCAN_ERROR_QRCVEMPTY,
PCAN_ERROR_BUSLIGHT,
@@ -58,6 +62,8 @@
# Set up logging
log = logging.getLogger("can.pcan")
+MIN_PCAN_API_VERSION = version.parse("4.2.0")
+
try:
# use the "uptime" library if available
@@ -206,6 +212,19 @@ def __init__(
self.m_objPCANBasic = PCANBasic()
self.m_PcanHandle = channel
+ error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION)
+ if error != PCAN_ERROR_OK:
+ raise CanInitializationError(
+ F"Failed to read pcan basic api version"
+ )
+
+ apv = version.parse(value.decode('ascii'))
+ if apv < MIN_PCAN_API_VERSION:
+ raise CanInitializationError(
+ F"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
+ F" Installed version is {apv}. Consider upgrade of pcan basic package"
+ )
+
if state is BusState.ACTIVE or state is BusState.PASSIVE:
self.state = state
else:
diff --git a/setup.py b/setup.py
index 31318ac06..fe0557d30 100644
--- a/setup.py
+++ b/setup.py
@@ -90,6 +90,7 @@
"typing_extensions>=3.10.0.0",
'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"',
'msgpack~=1.0.0;platform_system!="Windows"',
+ "packaging",
],
extras_require=extras_require,
)
From 56ad81c03205f53c104132ce0fbb504b3dac036b Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 13 Dec 2021 10:25:16 +0000
Subject: [PATCH 002/475] Format code with black
---
can/interfaces/pcan/pcan.py | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index b1fef17d1..27e8e4875 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -106,7 +106,7 @@ def __init__(
state=BusState.ACTIVE,
bitrate=500000,
*args,
- **kwargs
+ **kwargs,
):
"""A PCAN USB interface to CAN.
@@ -214,15 +214,13 @@ def __init__(
error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION)
if error != PCAN_ERROR_OK:
- raise CanInitializationError(
- F"Failed to read pcan basic api version"
- )
+ raise CanInitializationError(f"Failed to read pcan basic api version")
- apv = version.parse(value.decode('ascii'))
+ apv = version.parse(value.decode("ascii"))
if apv < MIN_PCAN_API_VERSION:
raise CanInitializationError(
- F"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
- F" Installed version is {apv}. Consider upgrade of pcan basic package"
+ f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
+ f" Installed version is {apv}. Consider upgrade of pcan basic package"
)
if state is BusState.ACTIVE or state is BusState.PASSIVE:
From 0f80d2548cfd6aaab124966f87b2424346d12715 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 09:50:42 +0100
Subject: [PATCH 003/475] Mover read of api version to separate function
---
can/interfaces/pcan/pcan.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 27e8e4875..9380e0663 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -212,11 +212,7 @@ def __init__(
self.m_objPCANBasic = PCANBasic()
self.m_PcanHandle = channel
- error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION)
- if error != PCAN_ERROR_OK:
- raise CanInitializationError(f"Failed to read pcan basic api version")
-
- apv = version.parse(value.decode("ascii"))
+ apv = self.get_api_version()
if apv < MIN_PCAN_API_VERSION:
raise CanInitializationError(
f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
@@ -321,6 +317,13 @@ def bits(n):
return complete_text
+ def get_api_version(self):
+ error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION)
+ if error != PCAN_ERROR_OK:
+ raise CanInitializationError(f"Failed to read pcan basic api version")
+
+ return version.parse(value.decode("ascii"))
+
def status(self):
"""
Query the PCAN bus status.
From ae260cdf3a1776dd863ad096bcde619da91d9deb Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 09:52:30 +0100
Subject: [PATCH 004/475] Move check of api version to separate method
---
can/interfaces/pcan/pcan.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 9380e0663..5ffcb94bc 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -212,12 +212,7 @@ def __init__(
self.m_objPCANBasic = PCANBasic()
self.m_PcanHandle = channel
- apv = self.get_api_version()
- if apv < MIN_PCAN_API_VERSION:
- raise CanInitializationError(
- f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
- f" Installed version is {apv}. Consider upgrade of pcan basic package"
- )
+ self.check_api_version()
if state is BusState.ACTIVE or state is BusState.PASSIVE:
self.state = state
@@ -324,6 +319,14 @@ def get_api_version(self):
return version.parse(value.decode("ascii"))
+ def check_api_version(self):
+ apv = self.get_api_version()
+ if apv < MIN_PCAN_API_VERSION:
+ raise CanInitializationError(
+ f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
+ f" Installed version is {apv}. Consider upgrade of pcan basic package"
+ )
+
def status(self):
"""
Query the PCAN bus status.
From bc66b5758d238794a29ff08073305689635a8eec Mon Sep 17 00:00:00 2001
From: Simon Tegelid
Date: Fri, 17 Dec 2021 12:40:49 +0100
Subject: [PATCH 005/475] Add preserve timestamps to virtual
Add an option to virtual interfaces to preserve message timestamps
on transmissions. This is useful in test setups fed by log files.
---
can/interfaces/virtual.py | 4 +++-
doc/interfaces/virtual.rst | 29 +++++++++++++++++++++++-
test/test_interface_virtual.py | 40 ++++++++++++++++++++++++++++++++++
3 files changed, 71 insertions(+), 2 deletions(-)
create mode 100644 test/test_interface_virtual.py
diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py
index a903435ac..ffd5b0241 100644
--- a/can/interfaces/virtual.py
+++ b/can/interfaces/virtual.py
@@ -59,6 +59,7 @@ def __init__(
channel: Any = None,
receive_own_messages: bool = False,
rx_queue_size: int = 0,
+ preserve_timestamps: bool = False,
**kwargs: Any,
) -> None:
super().__init__(
@@ -69,6 +70,7 @@ def __init__(
self.channel_id = channel
self.channel_info = f"Virtual bus channel {self.channel_id}"
self.receive_own_messages = receive_own_messages
+ self.preserve_timestamps = preserve_timestamps
self._open = True
with channels_lock:
@@ -103,7 +105,7 @@ def _recv_internal(
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
self._check_if_open()
- timestamp = time.time()
+ timestamp = msg.timestamp if self.preserve_timestamps else time.time()
# Add message to all listening on this channel
all_sent = True
for bus_queue in self.channel:
diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst
index b3fa7b38e..9258c9bbd 100644
--- a/doc/interfaces/virtual.rst
+++ b/doc/interfaces/virtual.rst
@@ -85,7 +85,7 @@ Example
-------
.. code-block:: python
-
+
import can
bus1 = can.interface.Bus('test', bustype='virtual')
@@ -100,6 +100,33 @@ Example
assert msg1.data == msg2.data
assert msg1.timestamp != msg2.timestamp
+.. code-block:: python
+
+ import can
+
+ bus1 = can.interface.Bus('test', bustype='virtual', preserve_timestamps=True)
+ bus2 = can.interface.Bus('test', bustype='virtual')
+
+ msg1 = can.Message(timestamp=1639740470.051948, arbitration_id=0xabcde, data=[1,2,3])
+
+ # Messages sent on bus1 will have their timestamps preserved when received
+ # on bus2
+ bus1.send(msg1)
+ msg2 = bus2.recv()
+
+ assert msg1.arbitration_id == msg2.arbitration_id
+ assert msg1.data == msg2.data
+ assert msg1.timestamp == msg2.timestamp
+
+ # Messages sent on bus2 will not have their timestamps preserved when
+ # received on bus1
+ bus2.send(msg1)
+ msg3 = bus1.recv()
+
+ assert msg1.arbitration_id == msg3.arbitration_id
+ assert msg1.data == msg3.data
+ assert msg1.timestamp != msg3.timestamp
+
Bus Class Documentation
-----------------------
diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py
new file mode 100644
index 000000000..009722779
--- /dev/null
+++ b/test/test_interface_virtual.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+"""
+This module tests :meth:`can.interface.virtual`.
+"""
+
+import unittest
+
+from can import Bus, Message
+
+EXAMPLE_MSG1 = Message(timestamp=1639739471.5565314, arbitration_id=0x481, data=b"\x01")
+
+
+class TestMessageFiltering(unittest.TestCase):
+ def setUp(self):
+ self.node1 = Bus("test", bustype="virtual", preserve_timestamps=True)
+ self.node2 = Bus("test", bustype="virtual")
+
+ def tearDown(self):
+ self.node1.shutdown()
+ self.node2.shutdown()
+
+ def test_sendmsg(self):
+ self.node2.send(EXAMPLE_MSG1)
+ r = self.node1.recv(0.1)
+ assert r.timestamp != EXAMPLE_MSG1.timestamp
+ assert r.arbitration_id == EXAMPLE_MSG1.arbitration_id
+ assert r.data == EXAMPLE_MSG1.data
+
+ def test_sendmsg_preserve_timestamp(self):
+ self.node1.send(EXAMPLE_MSG1)
+ r = self.node2.recv(0.1)
+ assert r.timestamp == EXAMPLE_MSG1.timestamp
+ assert r.arbitration_id == EXAMPLE_MSG1.arbitration_id
+ assert r.data == EXAMPLE_MSG1.data
+
+
+if __name__ == "__main__":
+ unittest.main()
From 4addd5f094c3760048d271b2bdc3c25ba8581e4a Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 09:57:10 +0100
Subject: [PATCH 006/475] Add mock for pcan tests to suppress check of api
version
---
test/test_pcan.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index b9cecff26..7d93781c0 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -26,6 +26,7 @@ def setUp(self) -> None:
self.mock_pcan.Initialize.return_value = PCAN_ERROR_OK
self.mock_pcan.InitializeFD = Mock(return_value=PCAN_ERROR_OK)
self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK)
+ self.mock_pcan.GetValue = self._mockGetValue
self.bus = None
@@ -34,6 +35,17 @@ def tearDown(self) -> None:
self.bus.shutdown()
self.bus = None
+ def _mockGetValue(self, Channel, Parameter):
+ """
+ This method is used as mock for GetValue method of PCANBasic object.
+ Only a subset of parameters are supported.
+ """
+ if Parameter == PCAN_API_VERSION:
+ return PCAN_ERROR_OK, "4.2".encode("ascii")
+ raise NotImplementedError(
+ f"No mock return value specified for parameter {Parameter}"
+ )
+
def test_bus_creation(self) -> None:
self.bus = can.Bus(bustype="pcan")
self.assertIsInstance(self.bus, PcanBus)
From 16e35f1fc291f80a9932c52f99f706d6de873fa2 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 21:11:14 +0100
Subject: [PATCH 007/475] Change mock/init order to make tests run
---
test/test_pcan.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 7d93781c0..74d3e4d6c 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -120,8 +120,11 @@ def test_reset(self, name, status, expected_result) -> None:
)
def test_get_device_number(self, name, status, expected_result) -> None:
with self.subTest(name):
- self.mock_pcan.GetValue = Mock(return_value=(status, 1))
self.bus = can.Bus(bustype="pcan", fd=True)
+ # Mock GetValue after creation of bus to use first mock of
+ # GetValue in constructor
+ self.mock_pcan.GetValue = Mock(return_value=(status, 1))
+
self.assertEqual(self.bus.get_device_number(), expected_result)
self.mock_pcan.GetValue.assert_called_once_with(
PCAN_USBBUS1, PCAN_DEVICE_NUMBER
From 1dcc736f8f61164ca68c6ce098b90a4b4aae3bd2 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 21:17:45 +0100
Subject: [PATCH 008/475] Put simulated version to object attribute
---
test/test_pcan.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 74d3e4d6c..452161c07 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -27,6 +27,7 @@ def setUp(self) -> None:
self.mock_pcan.InitializeFD = Mock(return_value=PCAN_ERROR_OK)
self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK)
self.mock_pcan.GetValue = self._mockGetValue
+ self.PCAN_API_VERSION_SIM = "4.2"
self.bus = None
@@ -41,7 +42,7 @@ def _mockGetValue(self, Channel, Parameter):
Only a subset of parameters are supported.
"""
if Parameter == PCAN_API_VERSION:
- return PCAN_ERROR_OK, "4.2".encode("ascii")
+ return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
raise NotImplementedError(
f"No mock return value specified for parameter {Parameter}"
)
From 5a43bb8dc958d25b70309ce7ac466190a7d75690 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 21:22:10 +0100
Subject: [PATCH 009/475] Add test for wrong api version
---
test/test_pcan.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 452161c07..2d1a67486 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -12,6 +12,7 @@
import can
from can.bus import BusState
+from can.exceptions import CanInitializationError
from can.interfaces.pcan.basic import *
from can.interfaces.pcan import PcanBus, PcanError
@@ -65,6 +66,11 @@ def test_bus_creation_fd(self) -> None:
self.mock_pcan.Initialize.assert_not_called()
self.mock_pcan.InitializeFD.assert_called_once()
+ def test_api_version_error(self) -> None:
+ self.PCAN_API_VERSION_SIM = "1.0"
+ with self.assertRaises(CanInitializationError):
+ self.bus = can.Bus(bustype="pcan")
+
@parameterized.expand(
[
("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"),
From 0ee529cb79a8729c3992c652ccc0315eb83f874f Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 17 Dec 2021 21:26:53 +0100
Subject: [PATCH 010/475] Add testcase test_api_version_read_fail
---
test/test_pcan.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 2d1a67486..459320de8 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -71,6 +71,11 @@ def test_api_version_error(self) -> None:
with self.assertRaises(CanInitializationError):
self.bus = can.Bus(bustype="pcan")
+ def test_api_version_read_fail(self) -> None:
+ self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None))
+ with self.assertRaises(CanInitializationError):
+ self.bus = can.Bus(bustype="pcan")
+
@parameterized.expand(
[
("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"),
From f1c5c0bd672c8a2195b2027323593afb1694438d Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 07:43:33 +0100
Subject: [PATCH 011/475] Log warning instead of raise error when pcan api is
to old
---
can/interfaces/pcan/pcan.py | 2 +-
test/test_pcan.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 5ffcb94bc..b8a0ebee5 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -322,7 +322,7 @@ def get_api_version(self):
def check_api_version(self):
apv = self.get_api_version()
if apv < MIN_PCAN_API_VERSION:
- raise CanInitializationError(
+ log.warning(
f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
f" Installed version is {apv}. Consider upgrade of pcan basic package"
)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 459320de8..b9f318c6f 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -66,9 +66,9 @@ def test_bus_creation_fd(self) -> None:
self.mock_pcan.Initialize.assert_not_called()
self.mock_pcan.InitializeFD.assert_called_once()
- def test_api_version_error(self) -> None:
+ def test_api_version_low(self) -> None:
self.PCAN_API_VERSION_SIM = "1.0"
- with self.assertRaises(CanInitializationError):
+ with self.assertLogs('can.pcan', level='WARNING') as cm:
self.bus = can.Bus(bustype="pcan")
def test_api_version_read_fail(self) -> None:
From 8f0136a57bcf26db57cf31388d533c9189394c4a Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 07:49:25 +0100
Subject: [PATCH 012/475] Add test for log output
---
test/test_pcan.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index b9f318c6f..e8c960f6b 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -68,8 +68,16 @@ def test_bus_creation_fd(self) -> None:
def test_api_version_low(self) -> None:
self.PCAN_API_VERSION_SIM = "1.0"
- with self.assertLogs('can.pcan', level='WARNING') as cm:
+ with self.assertLogs("can.pcan", level="WARNING") as cm:
self.bus = can.Bus(bustype="pcan")
+ found_version_warning = False
+ for i in cm.output:
+ if "version" in i and "pcan" in i:
+ found_version_warning = True
+ self.assertTrue(
+ found_version_warning,
+ f"No warning was logged for incompatible api version {cm.output}",
+ )
def test_api_version_read_fail(self) -> None:
self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None))
From 89deff5434b8dab404e2d4a4dd320712672b56b6 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Mon, 20 Dec 2021 19:03:00 +0100
Subject: [PATCH 013/475] Fix syntax highlighting in bus.rst
---
doc/bus.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/doc/bus.rst b/doc/bus.rst
index 1fbe771cb..bbe52cbd6 100644
--- a/doc/bus.rst
+++ b/doc/bus.rst
@@ -80,7 +80,9 @@ This thread safe version of the :class:`~can.BusABC` class can be used by multip
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.BusABC`::
+It can be used exactly like the normal :class:`~can.BusABC`:
+
+.. code-block:: python
# 'socketcan' is only an example interface, it works with all the others too
my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0')
From 0362acbcae9dcdcb97474cc97788fd1983184b84 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:05:51 +0100
Subject: [PATCH 014/475] Add test case for player.py
---
test/test_player.py | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100755 test/test_player.py
diff --git a/test/test_player.py b/test/test_player.py
new file mode 100755
index 000000000..62fdd0271
--- /dev/null
+++ b/test/test_player.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# coding: utf-8
+
+"""
+This module tests the functions inside of player.py
+"""
+
+import unittest
+from unittest import mock
+from unittest.mock import Mock
+import os
+import sys
+import can
+import can.player
+
+from .config import *
+
+
+class TestPlayerScriptModule(unittest.TestCase):
+ def setUp(self) -> None:
+ # Patch VirtualBus object
+ patcher_virtual_bus = mock.patch("can.interfaces.virtual.VirtualBus", spec=True)
+ self.MockVirtualBus = patcher_virtual_bus.start()
+ self.addCleanup(patcher_virtual_bus.stop)
+ self.mock_virtual_bus = self.MockVirtualBus.return_value
+ self.mock_virtual_bus.shutdown = Mock()
+
+ self.baseargs = [sys.argv[0], "-i", "virtual"]
+ self.logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc")
+
+ def assertSuccessfullCleanup(self):
+ self.MockVirtualBus.assert_called_once()
+
+ def test_play_virtual(self):
+ sys.argv = self.baseargs + [self.logfile]
+ can.player.main()
+ self.assertSuccessfullCleanup()
+
+
+if __name__ == "__main__":
+ unittest.main()
From ee069470224686764445cf8e8cd8e62a960a375e Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 18:06:52 +0000
Subject: [PATCH 015/475] Format code with black
---
test/test_player.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/test/test_player.py b/test/test_player.py
index 62fdd0271..1af48e429 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -26,7 +26,9 @@ def setUp(self) -> None:
self.mock_virtual_bus.shutdown = Mock()
self.baseargs = [sys.argv[0], "-i", "virtual"]
- self.logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc")
+ self.logfile = os.path.join(
+ os.path.dirname(__file__), "data", "test_CanMessage.asc"
+ )
def assertSuccessfullCleanup(self):
self.MockVirtualBus.assert_called_once()
From 5c72735a0d42fd10a159fec1f3cc1fb3a25da283 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:29:39 +0100
Subject: [PATCH 016/475] Add mock for sleep function to fasten up testing
---
test/test_player.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/test/test_player.py b/test/test_player.py
index 1af48e429..70a507ebe 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -25,6 +25,11 @@ def setUp(self) -> None:
self.mock_virtual_bus = self.MockVirtualBus.return_value
self.mock_virtual_bus.shutdown = Mock()
+ # Patch time sleep object
+ patcher_sleep = mock.patch("can.io.player.sleep", spec=True)
+ self.MockSleep = patcher_sleep.start()
+ self.addCleanup(patcher_sleep.stop)
+
self.baseargs = [sys.argv[0], "-i", "virtual"]
self.logfile = os.path.join(
os.path.dirname(__file__), "data", "test_CanMessage.asc"
From fec26fbdd6bf8008ea67ac12029ed6ec64b558ae Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:30:33 +0100
Subject: [PATCH 017/475] Add test for verbose mode
---
test/test_player.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/test/test_player.py b/test/test_player.py
index 70a507ebe..aeb63764b 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -43,6 +43,11 @@ def test_play_virtual(self):
can.player.main()
self.assertSuccessfullCleanup()
+ def test_play_virtual_verbose(self):
+ sys.argv = self.baseargs + ["-v", self.logfile]
+ can.player.main()
+ self.assertSuccessfullCleanup()
+
if __name__ == "__main__":
unittest.main()
From fefdd53972a5cbf2697b99df7211522b1b51e6ae Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:42:18 +0100
Subject: [PATCH 018/475] Add call count assert
---
test/test_player.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/test/test_player.py b/test/test_player.py
index aeb63764b..8fa1e45ef 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -41,11 +41,13 @@ def assertSuccessfullCleanup(self):
def test_play_virtual(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
+ self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
can.player.main()
+ self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
From 0907785c9025e49bcff09f77a2c7e9ba70475309 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:42:42 +0100
Subject: [PATCH 019/475] Add test for Keyboard interrupt during execution
---
test/test_player.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/test/test_player.py b/test/test_player.py
index 8fa1e45ef..12a20ccd8 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -50,6 +50,14 @@ def test_play_virtual_verbose(self):
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
+ def test_play_virtual_exit(self):
+ self.MockSleep.side_effect = KeyboardInterrupt
+
+ sys.argv = self.baseargs + [self.logfile]
+ can.player.main()
+ self.assertEqual(self.MockSleep.call_count, 1)
+ self.assertSuccessfullCleanup()
+
if __name__ == "__main__":
unittest.main()
From 4721134c40fbd33465f923ee2beae37b79ee3a34 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Mon, 20 Dec 2021 19:53:43 +0100
Subject: [PATCH 020/475] Add todos
---
test/test_player.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/test/test_player.py b/test/test_player.py
index 12a20ccd8..625c2842e 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -41,12 +41,15 @@ def assertSuccessfullCleanup(self):
def test_play_virtual(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
+ # TODO: add test two messages sent
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
can.player.main()
+ # TODO: add test two messages sent
+ # TODO: add test message was printed
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
@@ -55,9 +58,15 @@ def test_play_virtual_exit(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
+ # TODO: add test one message sent
self.assertEqual(self.MockSleep.call_count, 1)
self.assertSuccessfullCleanup()
+ def test_play_error_frame(self):
+ # TODO: implement
+ sys.argv = self.baseargs + ["--error-frames", self.logfile]
+ can.player.main()
+
if __name__ == "__main__":
unittest.main()
From 1d11a04d3fbb4eb9529d5180ac6fe28a6cc6931c Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 26 Dec 2021 14:28:24 +0100
Subject: [PATCH 021/475] Implement check for successfull cleanup and send
message call count
---
test/test_player.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index 625c2842e..eb8697a36 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -23,7 +23,7 @@ def setUp(self) -> None:
self.MockVirtualBus = patcher_virtual_bus.start()
self.addCleanup(patcher_virtual_bus.stop)
self.mock_virtual_bus = self.MockVirtualBus.return_value
- self.mock_virtual_bus.shutdown = Mock()
+ self.mock_virtual_bus.__enter__ = Mock(return_value=self.mock_virtual_bus)
# Patch time sleep object
patcher_sleep = mock.patch("can.io.player.sleep", spec=True)
@@ -37,11 +37,12 @@ def setUp(self) -> None:
def assertSuccessfullCleanup(self):
self.MockVirtualBus.assert_called_once()
+ self.mock_virtual_bus.__exit__.assert_called_once()
def test_play_virtual(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
- # TODO: add test two messages sent
+ self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
From bae496efa8f5de98f8d4c1f991a87636515791c0 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 26 Dec 2021 14:31:37 +0100
Subject: [PATCH 022/475] Add assumptions for test cases
---
test/test_player.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index eb8697a36..214f017c9 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -49,18 +49,18 @@ def test_play_virtual(self):
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
can.player.main()
- # TODO: add test two messages sent
# TODO: add test message was printed
+ self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
def test_play_virtual_exit(self):
- self.MockSleep.side_effect = KeyboardInterrupt
+ self.MockSleep.side_effect = [None, KeyboardInterrupt]
sys.argv = self.baseargs + [self.logfile]
can.player.main()
- # TODO: add test one message sent
- self.assertEqual(self.MockSleep.call_count, 1)
+ self.assertEqual(self.mock_virtual_bus.send.call_count, 1)
+ self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
def test_play_error_frame(self):
From c41845fb96c6611b99364c3480ed991bbe3889b8 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 26 Dec 2021 14:42:28 +0100
Subject: [PATCH 023/475] Add check for output on screen
---
test/test_player.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index 214f017c9..65fa5b6b9 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -10,6 +10,7 @@
from unittest.mock import Mock
import os
import sys
+import io
import can
import can.player
@@ -48,8 +49,10 @@ def test_play_virtual(self):
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
- can.player.main()
- # TODO: add test message was printed
+ with unittest.mock.patch('sys.stdout', new_callable=io.StringIO) as mock_stdout:
+ can.player.main()
+ self.assertIn('09 08 07 06 05 04 03 02', mock_stdout.getvalue())
+ self.assertIn('05 0c 00 00 00 00 00 00', mock_stdout.getvalue())
self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
From f6816b90f7f493e49bec7a97fea7a8c503e6308c Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 26 Dec 2021 14:56:10 +0100
Subject: [PATCH 024/475] Implement test for replay with error frames
---
test/data/logfile_errorframes.asc | 21 +++++++++++++++++++++
test/test_player.py | 19 +++++++++++++++++--
2 files changed, 38 insertions(+), 2 deletions(-)
create mode 100644 test/data/logfile_errorframes.asc
diff --git a/test/data/logfile_errorframes.asc b/test/data/logfile_errorframes.asc
new file mode 100644
index 000000000..bcb5584a7
--- /dev/null
+++ b/test/data/logfile_errorframes.asc
@@ -0,0 +1,21 @@
+date Sam Sep 30 15:06:13.191 2017
+base hex timestamps absolute
+internal events logged
+// version 9.0.0
+Begin Triggerblock Sam Sep 30 15:06:13.191 2017
+ 0.000000 Start of measurement
+ 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
+ 0.015991 CAN 2 Status:chip status error active
+ 2.501000 1 ErrorFrame
+ 2.501010 1 ErrorFrame ECC: 10100010
+ 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
+ 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
+ 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
+ 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x
+ 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784
+ 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x
+ 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x
+ 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x
+End TriggerBlock
diff --git a/test/test_player.py b/test/test_player.py
index 65fa5b6b9..ab275b81a 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -66,10 +66,25 @@ def test_play_virtual_exit(self):
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
+ def test_play_skip_error_frame(self):
+ logfile = os.path.join(
+ os.path.dirname(__file__), "data", "logfile_errorframes.asc"
+ )
+ sys.argv = self.baseargs + ["-v", logfile]
+ can.player.main()
+ self.assertEqual(self.mock_virtual_bus.send.call_count, 9)
+ self.assertEqual(self.MockSleep.call_count, 12)
+ self.assertSuccessfullCleanup()
+
def test_play_error_frame(self):
- # TODO: implement
- sys.argv = self.baseargs + ["--error-frames", self.logfile]
+ logfile = os.path.join(
+ os.path.dirname(__file__), "data", "logfile_errorframes.asc"
+ )
+ sys.argv = self.baseargs + ["-v", "--error-frames", logfile]
can.player.main()
+ self.assertEqual(self.mock_virtual_bus.send.call_count, 12)
+ self.assertEqual(self.MockSleep.call_count, 12)
+ self.assertSuccessfullCleanup()
if __name__ == "__main__":
From dcbb9631a742a8951ca19f3f8083300217abfae9 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 26 Dec 2021 13:57:12 +0000
Subject: [PATCH 025/475] Format code with black
---
test/test_player.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index ab275b81a..c1048aae6 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -49,10 +49,10 @@ def test_play_virtual(self):
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
- with unittest.mock.patch('sys.stdout', new_callable=io.StringIO) as mock_stdout:
+ with unittest.mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
can.player.main()
- self.assertIn('09 08 07 06 05 04 03 02', mock_stdout.getvalue())
- self.assertIn('05 0c 00 00 00 00 00 00', mock_stdout.getvalue())
+ self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue())
+ self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue())
self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfullCleanup()
From 558b2974187d65911c0ba19e9f6893cece20745d Mon Sep 17 00:00:00 2001
From: TJ
Date: Sat, 1 Jan 2022 12:53:21 -0800
Subject: [PATCH 026/475] Add str conversion to config val
---
can/util.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/util.py b/can/util.py
index e43a4d09d..9400259ce 100644
--- a/can/util.py
+++ b/can/util.py
@@ -232,7 +232,7 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
"btr1",
):
if key in config:
- timing_conf[key] = int(config[key], base=0)
+ timing_conf[key] = int(str(config[key]), base=0)
del config[key]
if timing_conf:
timing_conf["bitrate"] = config["bitrate"]
From eb8d92d797ad3517eb79743e6477392a56a1dc2e Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 10 Jan 2022 19:58:17 +0100
Subject: [PATCH 027/475] Fix typo
---
can/logger.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/logger.py b/can/logger.py
index d078cf2d3..053001968 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -61,7 +61,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
"extra_args",
nargs=argparse.REMAINDER,
help="""\
- The remainding arguments will be used for the interface initialisation.
+ The remaining arguments will be used for the interface initialisation.
For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent to
opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')`
""",
From 2e24af08326ecd69fba9f02fed7b9c26f233c92b Mon Sep 17 00:00:00 2001
From: Daniel Hrisca
Date: Tue, 11 Jan 2022 00:33:38 +0200
Subject: [PATCH 028/475] Fix vector get application config (#977)
* fixes #732: add support for VN8900 xlGetChannelTime function
* add another level of try/except according to the review
* format using black
* the application channels needs to be provided as a ctypes variable
* fix mock in test_vector.py
Co-authored-by: zariiii9003
---
can/interfaces/vector/canlib.py | 1 +
test/test_vector.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 21c6d0f1f..1313b00b7 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -688,6 +688,7 @@ def get_application_config(
hw_type = ctypes.c_uint()
hw_index = ctypes.c_uint()
hw_channel = ctypes.c_uint()
+ app_channel = ctypes.c_uint(app_channel)
xldriver.xlGetApplConfig(
app_name.encode(),
diff --git a/test/test_vector.py b/test/test_vector.py
index fc77dd48d..f7b1f99ce 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -356,7 +356,7 @@ def xlGetApplConfig(
bus_type: ctypes.c_uint,
) -> int:
hw_type.value = 1
- hw_channel.value = app_channel
+ hw_channel.value = 0
return 0
From 7da73032752a9aee22ac1e1d2717ae4fd78171df Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Tue, 11 Jan 2022 20:27:34 +0100
Subject: [PATCH 029/475] Remove useless import
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
test/test_player.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/test/test_player.py b/test/test_player.py
index c1048aae6..ca590a898 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -14,7 +14,6 @@
import can
import can.player
-from .config import *
class TestPlayerScriptModule(unittest.TestCase):
From 2cb76b51f8178d5c5b35d3a0a41e4651e35ef493 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Tue, 11 Jan 2022 19:28:31 +0000
Subject: [PATCH 030/475] Format code with black
---
test/test_player.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/test/test_player.py b/test/test_player.py
index ca590a898..a3b88d65a 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -15,7 +15,6 @@
import can.player
-
class TestPlayerScriptModule(unittest.TestCase):
def setUp(self) -> None:
# Patch VirtualBus object
From 5c6480bd03c3e0daf6891bd06baf8ca642ea304c Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Tue, 11 Jan 2022 20:31:59 +0100
Subject: [PATCH 031/475] Correct spell error
---
test/test_player.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index a3b88d65a..d510a3d00 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -34,7 +34,7 @@ def setUp(self) -> None:
os.path.dirname(__file__), "data", "test_CanMessage.asc"
)
- def assertSuccessfullCleanup(self):
+ def assertSuccessfulCleanup(self):
self.MockVirtualBus.assert_called_once()
self.mock_virtual_bus.__exit__.assert_called_once()
@@ -43,7 +43,7 @@ def test_play_virtual(self):
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
- self.assertSuccessfullCleanup()
+ self.assertSuccessfulCleanup()
def test_play_virtual_verbose(self):
sys.argv = self.baseargs + ["-v", self.logfile]
@@ -53,7 +53,7 @@ def test_play_virtual_verbose(self):
self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue())
self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
self.assertEqual(self.MockSleep.call_count, 2)
- self.assertSuccessfullCleanup()
+ self.assertSuccessfulCleanup()
def test_play_virtual_exit(self):
self.MockSleep.side_effect = [None, KeyboardInterrupt]
@@ -62,7 +62,7 @@ def test_play_virtual_exit(self):
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 1)
self.assertEqual(self.MockSleep.call_count, 2)
- self.assertSuccessfullCleanup()
+ self.assertSuccessfulCleanup()
def test_play_skip_error_frame(self):
logfile = os.path.join(
@@ -72,7 +72,7 @@ def test_play_skip_error_frame(self):
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 9)
self.assertEqual(self.MockSleep.call_count, 12)
- self.assertSuccessfullCleanup()
+ self.assertSuccessfulCleanup()
def test_play_error_frame(self):
logfile = os.path.join(
@@ -82,7 +82,7 @@ def test_play_error_frame(self):
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 12)
self.assertEqual(self.MockSleep.call_count, 12)
- self.assertSuccessfullCleanup()
+ self.assertSuccessfulCleanup()
if __name__ == "__main__":
From 07855c83ba4da8202bf17c4867fa1f6ecb62a683 Mon Sep 17 00:00:00 2001
From: TJ
Date: Thu, 13 Jan 2022 20:40:51 -0800
Subject: [PATCH 032/475] Add unittest for timing conf
---
test/test_util.py | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/test/test_util.py b/test/test_util.py
index bbfb9d580..7e3b10604 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -1,7 +1,7 @@
import unittest
import warnings
-from can.util import _rename_kwargs
+from can.util import _create_bus_config, _rename_kwargs
class RenameKwargsTest(unittest.TestCase):
@@ -47,3 +47,18 @@ def test_with_new_and_alias_present(self):
aliases = {"old_a": "a", "old_b": "b", "z": None}
with self.assertRaises(TypeError):
self._test(kwargs, aliases)
+
+
+class TestBusConfig(unittest.TestCase):
+ base_config = dict(interface="socketcan", bitrate=500_000)
+
+ def test_timing_can_use_int(self):
+ """
+ Test that an exception is not raised when using
+ integers for timing values in config.
+ """
+ timing_conf = dict(tseg1=5, tseg2=10, sjw=25)
+ try:
+ _create_bus_config({**self.base_config, **timing_conf})
+ except TypeError as e:
+ self.fail(e)
From 6f94d6b2791bda91bb80a332bd59c66dc11617ad Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 11:43:15 +0100
Subject: [PATCH 033/475] Update workflow versions, platforms, library and tool
versions and README to Python 3.7
---
.github/workflows/build.yml | 16 ++++++----------
.github/workflows/format-code.yml | 2 +-
README.rst | 4 ++--
requirements-lint.txt | 6 +++---
setup.py | 5 ++---
tox.ini | 6 +++---
6 files changed, 17 insertions(+), 22 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bb4c9a03b..95b295f90 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,16 +13,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
experimental: [false]
- python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.7"]
+ python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
include:
- # Skipping Py 3.10 on Windows until windows-curses has a cp310 wheel,
- # see https://github.com/zephyrproject-rtos/windows-curses/issues/26
+ # Only test on a single configuration while there are just pre-releases
- os: ubuntu-latest
- experimental: false
- python-version: "3.10"
- - os: macos-latest
- experimental: false
- python-version: "3.10"
+ experimental: true
+ python-version: "3.11.0-alpha.3"
fail-fast: false
steps:
- uses: actions/checkout@v2
@@ -38,7 +34,7 @@ jobs:
run: |
tox -e gh
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v1
+ uses: codecov/codecov-action@v2
with:
fail_ci_if_error: true
@@ -46,7 +42,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: Set up Python 3.10
+ - name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.10"
diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml
index 81e8fdf03..c3356e211 100644
--- a/.github/workflows/format-code.yml
+++ b/.github/workflows/format-code.yml
@@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: 3.9
+ python-version: 3.10
- name: Install dependencies
run: |
python -m pip install --upgrade pip
diff --git a/README.rst b/README.rst
index ac5537f7e..ebfa8431f 100644
--- a/README.rst
+++ b/README.rst
@@ -51,7 +51,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 currently supports Python 3.6+ as well as PyPy 3 and runs
+The library currently supports Python 3.7+ as well as PyPy 3 and runs
on Mac, Linux and Windows.
============================== ===========
@@ -59,7 +59,7 @@ Library Version Python
------------------------------ -----------
2.x 2.6+, 3.4+
3.x 2.7+, 3.5+
- 4.x *(currently on develop)* 3.6+
+ 4.x *(currently on develop)* 3.7+
============================== ===========
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 9aefb7415..55d985d54 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,4 +1,4 @@
-pylint==2.11.1
-black==21.10b0
-mypy==0.910
+pylint==2.12.2
+black==21.12b0
+mypy==0.931
mypy-extensions==0.4.3
diff --git a/setup.py b/setup.py
index 31318ac06..af47ecd4f 100644
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,6 @@
classifiers=[
# a list of all available ones: https://pypi.org/classifiers/
"Programming Language :: Python",
- "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
@@ -73,7 +72,7 @@
version=version,
packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]),
scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))),
- author="Python CAN contributors",
+ author="python-can contributors",
license="LGPL v3",
package_data={
"": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"],
@@ -82,7 +81,7 @@
},
# Installation
# see https://www.python.org/dev/peps/pep-0345/#version-specifiers
- python_requires=">=3.6",
+ python_requires=">=3.7",
install_requires=[
"setuptools",
"wrapt~=1.10",
diff --git a/tox.ini b/tox.ini
index 9964fb1e6..6b407dfeb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,11 +3,11 @@
[testenv]
deps =
pytest==6.2.*,>=6.2.5
- pytest-timeout==2.0.1
+ pytest-timeout==2.0.2
pytest-cov==3.0.0
- coverage==6.0.2
+ coverage==6.2
codecov==2.1.12
- hypothesis~=6.24.0
+ hypothesis~=6.35.0
pyserial~=3.5
parameterized~=0.8
From dd25e4b6feb7300028af4c5ce89a7646f85e684a Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 11:52:40 +0100
Subject: [PATCH 034/475] Fix code formatting job
---
.github/workflows/format-code.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml
index c3356e211..b86789662 100644
--- a/.github/workflows/format-code.yml
+++ b/.github/workflows/format-code.yml
@@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: 3.10
+ python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
@@ -22,10 +22,7 @@ jobs:
run: |
black --verbose .
- name: Commit Formated Code
- uses: EndBug/add-and-commit@v5
- env:
- # This is necessary in order to push a commit to the repo
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ uses: EndBug/add-and-commit@v7
with:
message: "Format code with black"
# Ref https://git-scm.com/docs/git-add#_examples
From 46ad7d46b92b3f1192c1511e67e3739becadf99f Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 12:00:27 +0100
Subject: [PATCH 035/475] Disable Python 3.11 pre-releases
---
.github/workflows/build.yml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 95b295f90..be222751c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,11 +14,12 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
experimental: [false]
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
- include:
+ # Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196
+ # include:
# Only test on a single configuration while there are just pre-releases
- - os: ubuntu-latest
- experimental: true
- python-version: "3.11.0-alpha.3"
+ # - os: ubuntu-latest
+ # experimental: true
+ # python-version: "3.11.0-alpha.3"
fail-fast: false
steps:
- uses: actions/checkout@v2
From 8641f5e02ec084b6754e38a7674aa72808a8de5c Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 12:01:07 +0100
Subject: [PATCH 036/475] Test that the auto-formatting tool can still commit
---
can/ctypesutil.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/ctypesutil.py b/can/ctypesutil.py
index 7c1e1f573..ab4cb11b5 100644
--- a/can/ctypesutil.py
+++ b/can/ctypesutil.py
@@ -14,7 +14,7 @@
try:
- _LibBase = ctypes.WinDLL # type: ignore
+ _LibBase = ctypes.WinDLL # type: ignore
_FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore
except AttributeError:
_LibBase = ctypes.CDLL
From 0acabbff21248b5c76039f026d31053711918147 Mon Sep 17 00:00:00 2001
From: felixdivo
Date: Sat, 15 Jan 2022 11:01:42 +0000
Subject: [PATCH 037/475] Format code with black
---
can/ctypesutil.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/ctypesutil.py b/can/ctypesutil.py
index ab4cb11b5..7c1e1f573 100644
--- a/can/ctypesutil.py
+++ b/can/ctypesutil.py
@@ -14,7 +14,7 @@
try:
- _LibBase = ctypes.WinDLL # type: ignore
+ _LibBase = ctypes.WinDLL # type: ignore
_FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore
except AttributeError:
_LibBase = ctypes.CDLL
From 954bb0295a845573d56a198c4b6c0d2d0fb8fdb7 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 21:37:48 +0100
Subject: [PATCH 038/475] Update README.rst
This way, the information is not given twice and we actually mention **C**Python.
---
README.rst | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.rst b/README.rst
index ac5537f7e..2d7f615e7 100644
--- a/README.rst
+++ b/README.rst
@@ -51,8 +51,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 currently supports Python 3.6+ as well as PyPy 3 and runs
-on Mac, Linux and Windows.
+The library currently supports CPython as well as PyPy and runs on Mac, Linux and Windows.
============================== ===========
Library Version Python
From 1dd4581dc36352ffa374caccdcdd068dde03af29 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sat, 15 Jan 2022 21:57:18 +0100
Subject: [PATCH 039/475] Implement test of function calls
---
test/test_player.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/test/test_player.py b/test/test_player.py
index d510a3d00..c73623c05 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -41,8 +41,29 @@ def assertSuccessfulCleanup(self):
def test_play_virtual(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
- self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
+ msg1 = can.Message(
+ timestamp=2.501,
+ arbitration_id=0xC8,
+ is_extended_id=False,
+ is_fd=False,
+ is_rx=False,
+ channel=1,
+ dlc=8,
+ data=[0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2],
+ )
+ msg2 = can.Message(
+ timestamp=17.876708,
+ arbitration_id=0x6F9,
+ is_extended_id=False,
+ is_fd=False,
+ is_rx=True,
+ channel=0,
+ dlc=8,
+ data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
+ )
self.assertEqual(self.MockSleep.call_count, 2)
+ self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]))
+ self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]))
self.assertSuccessfulCleanup()
def test_play_virtual_verbose(self):
From e7842f4fbff7ff45a56f061c180300809ce81238 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 15 Jan 2022 22:01:46 +0100
Subject: [PATCH 040/475] Add TL;DR
---
CHANGELOG.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index cf2a8b027..bcb2189cd 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,7 +1,7 @@
Version 4.0.0
====
-(In development)
+TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching.
Version 3.3.4
From 3382d5310db30359c387889ca2f76b8c71b031e2 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sat, 15 Jan 2022 22:45:00 +0100
Subject: [PATCH 041/475] Limit test for correct calls to python versions >=
3.8
---
test/test_player.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index c73623c05..a4f9ed4ad 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -62,8 +62,10 @@ def test_play_virtual(self):
data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
)
self.assertEqual(self.MockSleep.call_count, 2)
- self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]))
- self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]))
+ if sys.version_info.major > 3 or sys.version_info.minor >= 8:
+ # The args argument was introduced with python 3.8
+ self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]))
+ self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]))
self.assertSuccessfulCleanup()
def test_play_virtual_verbose(self):
From 67e409805e306fef778442d8c5d65d4c185324ee Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sat, 15 Jan 2022 21:45:54 +0000
Subject: [PATCH 042/475] Format code with black
---
test/test_player.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index a4f9ed4ad..c58ff936e 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -64,8 +64,12 @@ def test_play_virtual(self):
self.assertEqual(self.MockSleep.call_count, 2)
if sys.version_info.major > 3 or sys.version_info.minor >= 8:
# The args argument was introduced with python 3.8
- self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]))
- self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]))
+ self.assertTrue(
+ msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])
+ )
+ self.assertTrue(
+ msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])
+ )
self.assertSuccessfulCleanup()
def test_play_virtual_verbose(self):
From 81827517ee24fc3ddc3fc464574611ec054a2c06 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sat, 15 Jan 2022 22:50:04 +0100
Subject: [PATCH 043/475] Use lowercase parameter names
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
---
test/test_pcan.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index e8c960f6b..6fc21184a 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -37,15 +37,15 @@ def tearDown(self) -> None:
self.bus.shutdown()
self.bus = None
- def _mockGetValue(self, Channel, Parameter):
+ def _mockGetValue(self, channel, parameter):
"""
This method is used as mock for GetValue method of PCANBasic object.
Only a subset of parameters are supported.
"""
- if Parameter == PCAN_API_VERSION:
+ if parameter == PCAN_API_VERSION:
return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
raise NotImplementedError(
- f"No mock return value specified for parameter {Parameter}"
+ f"No mock return value specified for parameter {parameter}"
)
def test_bus_creation(self) -> None:
From a5f0ef80d604643a21f6c4880564143adfcbf9fb Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 16 Jan 2022 00:01:07 +0100
Subject: [PATCH 044/475] simplify version check
---
test/test_player.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/test_player.py b/test/test_player.py
index c58ff936e..efb446e9e 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -62,7 +62,7 @@ def test_play_virtual(self):
data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
)
self.assertEqual(self.MockSleep.call_count, 2)
- if sys.version_info.major > 3 or sys.version_info.minor >= 8:
+ if sys.version_info >= (3, 8):
# The args argument was introduced with python 3.8
self.assertTrue(
msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])
From 7a20405c5be293e7c293862fd096f126a4d20b89 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 16 Jan 2022 11:33:19 +0100
Subject: [PATCH 045/475] Change shebang and remove coding
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
---
test/test_player.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/test/test_player.py b/test/test_player.py
index efb446e9e..2f3307420 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python3
-# coding: utf-8
+#!/usr/bin/env python
"""
This module tests the functions inside of player.py
From 99d352ab6a22ad2c1e9212c76d5b4db927cbde93 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sun, 16 Jan 2022 11:41:21 +0100
Subject: [PATCH 046/475] Correct shebang and remove coding from all tests
---
test/test_bit_timing.py | 2 ++
test/test_cantact.py | 1 -
test/test_cyclic_socketcan.py | 2 ++
test/test_detect_available_configs.py | 1 -
test/test_interface_ixxat.py | 2 ++
test/test_interface_ixxat_fd.py | 2 ++
test/test_load_file_config.py | 1 -
test/test_logger.py | 1 -
test/test_message_class.py | 1 -
test/test_message_filtering.py | 1 -
test/test_message_sync.py | 1 -
test/test_neousys.py | 1 -
test/test_robotell.py | 1 -
test/test_scripts.py | 1 -
test/test_slcan.py | 1 -
test/test_socketcan.py | 2 ++
test/test_socketcan_helpers.py | 1 -
test/test_socketcan_loopback.py | 2 ++
test/test_util.py | 2 ++
test/test_vector.py | 1 -
test/test_viewer.py | 3 +--
test/zero_dlc_test.py | 1 -
22 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py
index 0b22e308f..2a9b1ac79 100644
--- a/test/test_bit_timing.py
+++ b/test/test_bit_timing.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
import can
diff --git a/test/test_cantact.py b/test/test_cantact.py
index 51bf569bb..e361ad1ad 100644
--- a/test/test_cantact.py
+++ b/test/test_cantact.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
Tests for CANtact interfaces
diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py
index 4e3887ad6..ca1db6bfc 100644
--- a/test/test_cyclic_socketcan.py
+++ b/test/test_cyclic_socketcan.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
This module tests multiple message cyclic send tasks.
"""
diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py
index 6e92ad9aa..f6590c276 100644
--- a/test/test_detect_available_configs.py
+++ b/test/test_detect_available_configs.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests :meth:`can.BusABC._detect_available_configs` and
diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py
index ccd985051..76285e422 100644
--- a/test/test_interface_ixxat.py
+++ b/test/test_interface_ixxat.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
Unittest for ixxat interface.
diff --git a/test/test_interface_ixxat_fd.py b/test/test_interface_ixxat_fd.py
index 0aa999a21..80060a7ed 100644
--- a/test/test_interface_ixxat_fd.py
+++ b/test/test_interface_ixxat_fd.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
Unittest for ixxat interface using fd option.
diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py
index 79b2e6c4b..c71e6ccd6 100644
--- a/test/test_load_file_config.py
+++ b/test/test_load_file_config.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
import shutil
import tempfile
diff --git a/test/test_logger.py b/test/test_logger.py
index a046b919f..9b749b859 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests the functions inside of logger.py
diff --git a/test/test_message_class.py b/test/test_message_class.py
index d0908e363..688cda24f 100644
--- a/test/test_message_class.py
+++ b/test/test_message_class.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
import unittest
import sys
diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py
index 18ddf9e19..addea13fd 100644
--- a/test/test_message_filtering.py
+++ b/test/test_message_filtering.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests :meth:`can.BusABC._matches_filters`.
diff --git a/test/test_message_sync.py b/test/test_message_sync.py
index 6e6a89a4d..1e2d61b24 100644
--- a/test/test_message_sync.py
+++ b/test/test_message_sync.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests :class:`can.MessageSync`.
diff --git a/test/test_neousys.py b/test/test_neousys.py
index c2ae535f5..f61c37655 100644
--- a/test/test_neousys.py
+++ b/test/test_neousys.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
import ctypes
import os
diff --git a/test/test_robotell.py b/test/test_robotell.py
index 58e2d9a7f..86e053f2d 100644
--- a/test/test_robotell.py
+++ b/test/test_robotell.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
import unittest
import can
diff --git a/test/test_scripts.py b/test/test_scripts.py
index 8efd70eff..a22820bd8 100644
--- a/test/test_scripts.py
+++ b/test/test_scripts.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests that the scripts are all callable.
diff --git a/test/test_slcan.py b/test/test_slcan.py
index 781fa75df..1e6282d41 100644
--- a/test/test_slcan.py
+++ b/test/test_slcan.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
import unittest
import can
diff --git a/test/test_socketcan.py b/test/test_socketcan.py
index c322bbf75..a2c4faed3 100644
--- a/test/test_socketcan.py
+++ b/test/test_socketcan.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
Test functions in `can.interfaces.socketcan.socketcan`.
"""
diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py
index 669491f43..f3fbe6d26 100644
--- a/test/test_socketcan_helpers.py
+++ b/test/test_socketcan_helpers.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
Tests helpers in `can.interfaces.socketcan.socketcan_common`.
diff --git a/test/test_socketcan_loopback.py b/test/test_socketcan_loopback.py
index 17b83b268..2934eb9ea 100644
--- a/test/test_socketcan_loopback.py
+++ b/test/test_socketcan_loopback.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
This module tests sending messages on socketcan with and without local_loopback flag
diff --git a/test/test_util.py b/test/test_util.py
index 7e3b10604..5768da282 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
import unittest
import warnings
diff --git a/test/test_vector.py b/test/test_vector.py
index f7b1f99ce..b1626b18c 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
Test for Vector Interface
diff --git a/test/test_viewer.py b/test/test_viewer.py
index 004877d7a..f2e3ef0e8 100644
--- a/test/test_viewer.py
+++ b/test/test_viewer.py
@@ -1,5 +1,4 @@
-#!/usr/bin/python
-# coding: utf-8
+#!/usr/bin/env python
#
# Copyright (C) 2018 Kristian Sloth Lauszus.
#
diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py
index 350d6aa4e..dd7c0dd49 100644
--- a/test/zero_dlc_test.py
+++ b/test/zero_dlc_test.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
"""
From 80352164b6530fa1691b974c5c84d0953bb9b7f5 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sun, 16 Jan 2022 17:15:38 +0100
Subject: [PATCH 047/475] Add changelog for 4.0.0
Also change the changelog file extension to Markdown
---
CHANGELOG.txt => CHANGELOG.md | 181 ++++++++++++++++++++++++++++++++++
setup.py | 2 +-
2 files changed, 182 insertions(+), 1 deletion(-)
rename CHANGELOG.txt => CHANGELOG.md (50%)
diff --git a/CHANGELOG.txt b/CHANGELOG.md
similarity index 50%
rename from CHANGELOG.txt
rename to CHANGELOG.md
index bcb2189cd..52b04e510 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.md
@@ -3,6 +3,187 @@ Version 4.0.0
TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching.
+For more than two years, there was no major release of *python-can*.
+However, development was very much active over most of this time, and many parts were switched out and improved.
+Over this time, over 530 issues and PRs have been resolved or merged, and discussions took place in even more.
+Statistics of the final diff: About 200 files changed due to ~22k additions and ~7k deletions from more than thirty contributors.
+
+This changelog diligently lists the major changes but does not promise to be the complete list of changes.
+Therefore, users are strongly advised to thoroughly test their programs against this new version.
+Re-reading the documentation for you interfaces might be helpful too as limitations and capabilities might have changed or are more explicit.
+While we did try to avoid breaking changes, in some cases it was not feasible and in particular many implementation details have changed.
+
+Major features
+--------------
+
+* Type hints for the core library and some interfaces (#652 and many others)
+* Support for Python 3.7-3.10+ only (dropped support for Python 2.* and 3.5-3.6) (#528 and many others)
+* [Granular and unified exceptions](https://python-can.readthedocs.io/en/develop/api.html#errors) (#356, #562, #1025; overview in #1046)
+* [Support for automatic configuration detection](https://python-can.readthedocs.io/en/develop/api.html#can.detect_available_configs) in most interfaces (#303, #640, #641, #811, #1077, #1085)
+* Better alignment of interfaces and IO to common conventions and semantics
+
+New interfaces
+--------------
+
+* udp_multicast (#644)
+* robotell (#731)
+* cantact (#853)
+* gs_usb (#905)
+* nixnet (#968, #1154)
+* neousys (#980, #1076)
+* socketcand (#1140)
+* etas (#1144)
+
+Improved interfaces
+-------------------
+
+* socketcan
+ * Support for multiple Cyclic Messages in Tasks (#610)
+ * Socketcan crash when attempting to stop CyclicSendTask with same arbitration ID (#605, #638, #720)
+ * Relax restriction of arbitration ID uniqueness for CyclicSendTask (#721, #785, #930)
+ * Add nanosecond resolution time stamping to socketcan (#938, #1015)
+ * Add support for changing the loopback flag (#960)
+ * Socketcan timestamps are missing sub-second precision (#1021, #1029)
+ * Add parameter to ignore CAN error frames (#1128)
+* socketcan_ctypes
+ * Removed and replaced by socketcan after deprecation period
+* socketcan_native
+ * Removed and replaced by socketcan after deprecation period
+* vector
+ * Add chip state API (#635)
+ * Add methods to handle non message events (#708)
+ * Implement XLbusParams (#718)
+ * Add support for VN8900 xlGetChannelTime function (#732, #733)
+ * Add vector hardware config popup (#774)
+ * Fix Vector CANlib treatment of empty app name (#796, #814)
+ * Make VectorError pickleable (#848)
+ * Add methods get_application_config(), set_application_config() and set_timer_rate() to VectorBus (#849)
+ * Interface arguments are now lowercase (#858)
+ * Fix errors using multiple Vector devices (#898, #971, #977)
+ * Add more interface information to channel config (#917)
+ * Improve timestamp accuracy on Windows (#934, #936)
+ * Fix error with VN8900 (#1184)
+* PCAN
+ * Do not incorrectly reset CANMsg.MSGTYPE on remote frame (#659, #681)
+ * Add support for error frames (#711)
+ * Added keycheck for windows platform for better error message (#724)
+ * Added status_string method to return simple status strings (#725)
+ * Fix timestamp timezone offset (#777, #778)
+ * Add [Cygwin](https://www.cygwin.com/) support (#840)
+ * Update PCAN basic Python file to February 7, 2020 (#929)
+ * Fix compatibility with the latest macOS SDK (#947, #948, #957, #976)
+ * Allow numerical channel specifier (#981, #982)
+ * macOS: Try to find libPCBUSB.dylib before loading it (#983, #984)
+ * Disable command PCAN_ALLOW_ERROR_FRAMES on macOS (#985)
+ * Force english error messages (#986, #993, #994)
+ * Add set/get device number (#987)
+ * Timestamps are silently incorrect on Windows without uptime installed (#1053, #1093)
+ * Implement check for minimum version of pcan library (#1065, #1188)
+ * Handle case where uptime is imported successfully but returns None (#1102, #1103)
+* slcan
+ * Fix bitrate setting (#691)
+ * Fix fileno crash on Windows (#924)
+* ics_neovi
+ * Filter out Tx error messages (#854)
+ * Adding support for send timeout (#855)
+ * Raising more precise API error when set bitrate fails (#865)
+ * Omit the transmit exception cause for brevity (#1086)
+ * Raise ValueError if message data is over max frame length (#1177, #1181)
+ * Setting is_error_frame message property (#1189)
+* ixxat
+ * Raise exception on busoff in recv() (#856)
+ * Add support for 666 kbit/s bitrate (#911)
+ * Add function to list hwids of available devices (#926)
+ * Add CAN FD support (#1119)
+* seeed
+ * Fix fileno crash on Windows (#902)
+* kvaser
+ * Improve timestamp accuracy on Windows (#934, #936)
+* usb2can
+ * Fix "Error 8" on Windows and provide better error messages (#989)
+* serial
+ * Fix "TypeError: cannot unpack non-iterable NoneType" and more robust error handling (#1000, #1010)
+* canalystii
+ * Fix is_extended_id (#1006)
+ * Fix transmitting onto a busy bus (#1114)
+ * Replace binary library with python driver (#726, #1127)
+
+Other API changes and improvements
+----------------------------------
+
+* CAN FD frame support is pretty complete (#963)
+ * ASCWriter (#604) and ASCReader (#741)
+ * Canutils reader and writer (#1042)
+ * Logger, viewer and player tools can handle CAN FD (#632)
+ * Many bugfixes and more testing coverage
+* IO
+ * Log rotation (#648, #874, #881, #1147)
+ * Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783)
+ * ASCReader/Writer enhancements (#820)
+ * Adding absolute timestamps to ASC reader (#761)
+ * Support other base number (radix) at ASCReader (#764)
+ * Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194)
+ * Adding support for gzipped ASC logging file (.asc.gz) (#1138)
+ * Improve [IO class hierarchy](https://python-can.readthedocs.io/en/develop/internal-api.html#module-can.io.generic) (#1147)
+* An [overview over various "virtual" interfaces](https://python-can.readthedocs.io/en/develop/interfaces/virtual.html#other-virtual-interfaces) (#644)
+* Make ThreadBasedCyclicSendTask event based & improve timing accuracy (#656)
+* Ignore error frames in can.player by default, add --error-frames option (#690)
+* Add __eq__ method to can.Message (#737, #747)
+* Add an error callback to ThreadBasedCyclicSendTask (#743, #781)
+* Add direction to CAN messages (#773, #779, #780, #852, #966)
+* Notifier no longer raises handled exceptions in rx_thread (#775, #789) but does so if no listener handles them (#1039, #1040)
+* Changes to serial device number decoding (#869)
+* Add a default fileno function to the BusABC (#877)
+* Disallow Messages to simultaneously be "FD" and "remote" (#1049)
+* Speed up interface plugin imports by removing pkg_resources (#1110)
+* Avoid flooding the logger with many errors when they are the same (#1125)
+* Allowing for extra config arguments in can.logger (#1142, #1170)
+* Add changed byte highlighting to viewer.py (#1159)
+
+Other Bugfixes
+--------------
+
+* BLF PDU padding (#459)
+* stop_all_periodic_tasks skipping every other task (#634, #637, #645)
+* Preserve capitalization when reading config files (#702, #1062)
+* ASCReader: Skip J1939Tp messages (#701)
+* Fix crash in Canutils Log Reader when parsing RTR frames (#713)
+* Various problems with the installation of the library
+* ASCWriter: Fix date format to show correct day of month (#754)
+* Fixes that some BLF files can't be read ( #763, #765)
+* Seek for start of object instead of calculating it (#786, #803, #806)
+* Only import winreg when on Windows (#800, #802)
+* Find the correct Reader/Writer independently of the file extension case (#895)
+* RecursionError when unpickling message object (#804, #885, #904)
+* Move "filelock" to neovi dependencies (#943)
+* Bus() with "fd" parameter as type bool always resolved to fd-enabled configuration (#954, #956)
+* Asyncio code hits error due to deprecated loop parameter (#1005, #1013)
+* Catch time before 1970 in ASCReader (#1034)
+* Fix a bug where error handlers were not called correctly (#1116)
+* Improved user interface of viewer script (#1118)
+* Correct app_name argument in logger (#1151)
+* Calling stop_all_periodic_tasks() in BusABC.shutdown() and all interfaces call it on shutdown (#1174)
+* Timing configurations do not allow int (#1175)
+* Some smaller bugfixes are not listed here since the problems were never part of a proper release
+
+Behind the scenes & Quality assurance
+-------------------------------------
+
+* We publish both source distributions (`sdist`) and binary wheels (`bdist_wheel`) (#1059, #1071)
+* Many interfaces were partly rewritten to modernize the code or to better handle errors
+* Performance improvements
+* Dependencies have changed
+* Derive type information in Sphinx docs directly from type hints (#654)
+* Better documentation in many, many places; This includes the examples, README and python-can developer resources
+* Add issue templates (#1008, #1017, #1018, #1178)
+* Many continuous integration (CI) discussions & improvements (for example: #951, #940, #1032)
+ * Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651)
+ * Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870)
+ * Use [Mergify](https://mergify.com/) (#821, #835, #937)
+ * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827)
+ * Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950)
+ * [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces
+* Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198)
Version 3.3.4
====
diff --git a/setup.py b/setup.py
index 31318ac06..1fa5c7f6b 100644
--- a/setup.py
+++ b/setup.py
@@ -76,7 +76,7 @@
author="Python CAN contributors",
license="LGPL v3",
package_data={
- "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"],
+ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"],
"doc": ["*.*"],
"examples": ["*.py"],
},
From 2ba9bd39e8a57afb68f1ac5de5208e925f86adb5 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sun, 16 Jan 2022 20:13:03 +0100
Subject: [PATCH 048/475] React to #1219
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52b04e510..80c765f93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -180,7 +180,7 @@ Behind the scenes & Quality assurance
* Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651)
* Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870)
* Use [Mergify](https://mergify.com/) (#821, #835, #937)
- * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827)
+ * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827)
* Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950)
* [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces
* Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198)
From 7226fa373ee51800ef7a77867af1e8155c14ebc5 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sun, 16 Jan 2022 20:35:31 +0100
Subject: [PATCH 049/475] Typos
---
CHANGELOG.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80c765f93..829f70ac4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,8 +10,8 @@ Statistics of the final diff: About 200 files changed due to ~22k additions and
This changelog diligently lists the major changes but does not promise to be the complete list of changes.
Therefore, users are strongly advised to thoroughly test their programs against this new version.
-Re-reading the documentation for you interfaces might be helpful too as limitations and capabilities might have changed or are more explicit.
-While we did try to avoid breaking changes, in some cases it was not feasible and in particular many implementation details have changed.
+Re-reading the documentation for your interfaces might be helpful too as limitations and capabilities might have changed or are more explicit.
+While we did try to avoid breaking changes, in some cases it was not feasible and in particular, many implementation details have changed.
Major features
--------------
From e86b8656b2d22fd2ca1f407ca5c448534ffd7b02 Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Mon, 17 Jan 2022 14:14:40 -0500
Subject: [PATCH 050/475] Moved #1125 to the neovi section
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 829f70ac4..b5676c96d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -87,6 +87,7 @@ Improved interfaces
* Filter out Tx error messages (#854)
* Adding support for send timeout (#855)
* Raising more precise API error when set bitrate fails (#865)
+ * Avoid flooding the logger with many errors when they are the same (#1125)
* Omit the transmit exception cause for brevity (#1086)
* Raise ValueError if message data is over max frame length (#1177, #1181)
* Setting is_error_frame message property (#1189)
@@ -136,7 +137,6 @@ Other API changes and improvements
* Add a default fileno function to the BusABC (#877)
* Disallow Messages to simultaneously be "FD" and "remote" (#1049)
* Speed up interface plugin imports by removing pkg_resources (#1110)
-* Avoid flooding the logger with many errors when they are the same (#1125)
* Allowing for extra config arguments in can.logger (#1142, #1170)
* Add changed byte highlighting to viewer.py (#1159)
From a4154975696c171118115438884547be0ca8f977 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Tue, 18 Jan 2022 10:29:16 +0100
Subject: [PATCH 051/475] Update CHANGELOG.md
Message.__eq__ is gone. No need to mention it here.
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
CHANGELOG.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5676c96d..da451f157 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -129,7 +129,6 @@ Other API changes and improvements
* An [overview over various "virtual" interfaces](https://python-can.readthedocs.io/en/develop/interfaces/virtual.html#other-virtual-interfaces) (#644)
* Make ThreadBasedCyclicSendTask event based & improve timing accuracy (#656)
* Ignore error frames in can.player by default, add --error-frames option (#690)
-* Add __eq__ method to can.Message (#737, #747)
* Add an error callback to ThreadBasedCyclicSendTask (#743, #781)
* Add direction to CAN messages (#773, #779, #780, #852, #966)
* Notifier no longer raises handled exceptions in rx_thread (#775, #789) but does so if no listener handles them (#1039, #1040)
From f7f9a8db3c137584fee48fba14e8589e71942bb4 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 20 Jan 2022 15:27:50 +0100
Subject: [PATCH 052/475] move static code analysis to Github actions
---
.github/workflows/build.yml | 39 +++++++++++++++++++++++++++++++++++++
.travis.yml | 17 ----------------
requirements-lint.txt | 1 +
setup.cfg | 2 +-
4 files changed, 41 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index be222751c..21eec2048 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,6 +39,45 @@ jobs:
with:
fail_ci_if_error: true
+ static-code-analysis:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -e .
+ pip install -r requirements-lint.txt
+ - name: mypy 3.7
+ continue-on-error: true
+ run: |
+ mypy --python-version 3.7 .
+ - name: mypy 3.8
+ continue-on-error: true
+ run: |
+ mypy --python-version 3.8 .
+ - name: mypy 3.9
+ continue-on-error: true
+ run: |
+ mypy --python-version 3.9 .
+ - name: mypy 3.10
+ continue-on-error: true
+ run: |
+ mypy --python-version 3.10 .
+ - name: pylint
+ continue-on-error: true
+ run: |
+ pylint --rcfile=.pylintrc \
+ can/**.py \
+ setup.py \
+ doc.conf \
+ scripts/**.py \
+ examples/**.py
+
format:
runs-on: ubuntu-latest
steps:
diff --git a/.travis.yml b/.travis.yml
index 77eef3699..fb24f8e9a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -74,23 +74,6 @@ jobs:
# -a Write all files
# -n nitpicky
- python -m sphinx -an doc build
- - stage: linter
- name: "Linter Checks"
- python: "3.9"
- before_install:
- - travis_retry pip install -r requirements-lint.txt
- script:
- # -------------
- # pylint checking:
- # check the entire main codebase (except the tests)
- - pylint --rcfile=.pylintrc can/**.py setup.py doc.conf scripts/**.py examples/**.py
- # -------------
- # mypy checking:
- - mypy
- can/*.py
- can/io/**.py
- scripts/**.py
- examples/**.py
- stage: deploy
name: "PyPi Deployment"
python: "3.9"
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 55d985d54..e9ad9106c 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -2,3 +2,4 @@ pylint==2.12.2
black==21.12b0
mypy==0.931
mypy-extensions==0.4.3
+types-setuptools
diff --git a/setup.cfg b/setup.cfg
index 84d734573..48a688026 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,7 +2,6 @@
license_file = LICENSE.txt
[mypy]
-python_version = 3.7
warn_return_any = True
warn_unused_configs = True
ignore_missing_imports = True
@@ -10,3 +9,4 @@ no_implicit_optional = True
disallow_incomplete_defs = True
warn_redundant_casts = True
warn_unused_ignores = True
+exclude = (^venv|^test|^can/interfaces|^setup.py$)
\ No newline at end of file
From bc14b95ce354025e3aed7a8b4c6e3e668a91ceb1 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 20 Jan 2022 15:35:10 +0100
Subject: [PATCH 053/475] move continue-on-error
---
.github/workflows/build.yml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 21eec2048..1f57103a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,6 +41,7 @@ jobs:
static-code-analysis:
runs-on: ubuntu-latest
+ continue-on-error: true
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -53,23 +54,18 @@ jobs:
pip install -e .
pip install -r requirements-lint.txt
- name: mypy 3.7
- continue-on-error: true
run: |
mypy --python-version 3.7 .
- name: mypy 3.8
- continue-on-error: true
run: |
mypy --python-version 3.8 .
- name: mypy 3.9
- continue-on-error: true
run: |
mypy --python-version 3.9 .
- name: mypy 3.10
- continue-on-error: true
run: |
mypy --python-version 3.10 .
- name: pylint
- continue-on-error: true
run: |
pylint --rcfile=.pylintrc \
can/**.py \
From c0da4c857b796178807531e4ea1c1887a03281bb Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 20 Jan 2022 16:43:26 +0100
Subject: [PATCH 054/475] make mypy happy
---
can/ctypesutil.py | 8 ++++----
can/exceptions.py | 10 +++++++---
can/io/asc.py | 17 +++++++++--------
can/io/blf.py | 6 +++++-
can/io/canutils.py | 4 +++-
can/io/csv.py | 3 +++
can/io/generic.py | 9 +++++----
can/io/logger.py | 7 +++++--
can/io/printer.py | 7 ++++---
can/typechecking.py | 2 +-
10 files changed, 46 insertions(+), 27 deletions(-)
diff --git a/can/ctypesutil.py b/can/ctypesutil.py
index 7c1e1f573..4cfebb5b8 100644
--- a/can/ctypesutil.py
+++ b/can/ctypesutil.py
@@ -1,7 +1,7 @@
+# type: ignore
"""
This module contains common `ctypes` utils.
"""
-
import ctypes
import logging
import sys
@@ -14,14 +14,14 @@
try:
- _LibBase = ctypes.WinDLL # type: ignore
- _FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore
+ _LibBase = ctypes.WinDLL
+ _FUNCTION_TYPE = ctypes.WINFUNCTYPE
except AttributeError:
_LibBase = ctypes.CDLL
_FUNCTION_TYPE = ctypes.CFUNCTYPE
-class CLibrary(_LibBase): # type: ignore
+class CLibrary(_LibBase):
def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None:
self.func_name: Any
diff --git a/can/exceptions.py b/can/exceptions.py
index e8731737c..aec0dfd1d 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -14,13 +14,17 @@
For example, validating typical arguments and parameters might result in a
:class:`ValueError`. This should always be documented for the function at hand.
"""
-
-
+import sys
from contextlib import contextmanager
from typing import Optional
from typing import Type
+if sys.version_info >= (3, 9):
+ from collections.abc import Generator
+else:
+ from typing import Generator
+
class CanError(Exception):
"""Base class for all CAN related exceptions.
@@ -106,7 +110,7 @@ class CanTimeoutError(CanError, TimeoutError):
def error_check(
error_message: Optional[str] = None,
exception_type: Type[CanError] = CanOperationError,
-) -> None:
+) -> Generator[None, None, None]:
"""Catches any exceptions and turns them into the new type while preserving the stack trace."""
try:
yield
diff --git a/can/io/asc.py b/can/io/asc.py
index 4e78d7528..74f630f43 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -6,7 +6,7 @@
- under `test/data/logfile.asc`
"""
import gzip
-from typing import cast, Any, Generator, IO, List, Optional, Dict, Union
+from typing import cast, Any, Generator, IO, List, Optional, Dict, Union, TextIO
from datetime import datetime
import time
@@ -34,6 +34,8 @@ class ASCReader(BaseIOHandler):
bus statistics, J1939 Transport Protocol messages) is ignored.
"""
+ file: TextIO
+
FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y"
def __init__(
@@ -205,8 +207,6 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag
return Message(**msg_kwargs)
def __iter__(self) -> Generator[Message, None, None]:
- # This is guaranteed to not be None since we raise ValueError in __init__
- self.file = cast(IO[Any], self.file)
self._extract_header()
for line in self.file:
@@ -214,10 +214,11 @@ def __iter__(self) -> Generator[Message, None, None]:
if not temp or not temp[0].isdigit():
# Could be a comment
continue
- msg_kwargs = {}
+
+ msg_kwargs: Dict[str, Union[float, bool, int]] = {}
try:
- timestamp, channel, rest_of_message = temp.split(None, 2)
- timestamp = float(timestamp) + self.start_time
+ _timestamp, channel, rest_of_message = temp.split(None, 2)
+ timestamp = float(_timestamp) + self.start_time
msg_kwargs["timestamp"] = timestamp
if channel == "CANFD":
msg_kwargs["is_fd"] = True
@@ -250,6 +251,8 @@ class ASCWriter(FileIOMessageWriter, Listener):
It the first message does not have a timestamp, it is set to zero.
"""
+ file: TextIO
+
FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}"
FORMAT_MESSAGE_FD = " ".join(
[
@@ -319,8 +322,6 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None:
if not message: # if empty or None
logger.debug("ASCWriter: ignoring empty message")
return
- # This is guaranteed to not be None since we raise ValueError in __init__
- self.file = cast(IO[Any], self.file)
# this is the case for the very first message:
if not self.header_written:
diff --git a/can/io/blf.py b/can/io/blf.py
index 9bb54d984..346d66cf6 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -17,7 +17,7 @@
import datetime
import time
import logging
-from typing import List
+from typing import List, BinaryIO
from ..message import Message
from ..listener import Listener
@@ -139,6 +139,8 @@ class BLFReader(BaseIOHandler):
silently ignored.
"""
+ file: BinaryIO
+
def __init__(self, file: AcceptedIOType) -> None:
"""
:param file: a path-like object or as file-like object to read from
@@ -352,6 +354,8 @@ class BLFWriter(FileIOMessageWriter, Listener):
Logs CAN data to a Binary Logging File compatible with Vector's tools.
"""
+ file: BinaryIO
+
#: Max log container size of uncompressed data
max_container_size = 128 * 1024
diff --git a/can/io/canutils.py b/can/io/canutils.py
index 5aa4ad53b..d3e122ae5 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -114,7 +114,9 @@ class CanutilsLogWriter(FileIOMessageWriter, Listener):
It the first message does not have a timestamp, it is set to zero.
"""
- def __init__(self, file: AcceptedIOType, channel="vcan0", append=False):
+ def __init__(
+ self, file: AcceptedIOType, channel: str = "vcan0", append: bool = False
+ ):
"""
:param file: a path-like object or as file-like object to write to
If this is a file-like object, is has to opened in text
diff --git a/can/io/csv.py b/can/io/csv.py
index 6e39e0096..fa3175a2d 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -10,6 +10,7 @@
"""
from base64 import b64encode, b64decode
+from typing import TextIO
from can.message import Message
from can.listener import Listener
@@ -82,6 +83,8 @@ class CSVWriter(FileIOMessageWriter, Listener):
Each line is terminated with a platform specific line separator.
"""
+ file: TextIO
+
def __init__(self, file: AcceptedIOType, append: bool = False) -> None:
"""
:param file: a path-like object or a file-like object to write to.
diff --git a/can/io/generic.py b/can/io/generic.py
index 1606c444d..96ff91abf 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -45,7 +45,10 @@ def __init__(
else:
# pylint: disable=consider-using-with
# file is some path-like object
- self.file = open(cast(can.typechecking.StringPathLike, file), mode)
+ self.file = cast(
+ can.typechecking.FileLike,
+ open(cast(can.typechecking.StringPathLike, file), mode),
+ )
# for multiple inheritance
super().__init__()
@@ -80,9 +83,7 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
file: Union[TextIO, BinaryIO]
- def __init__(
- self, file: Union[can.typechecking.FileLike, TextIO, BinaryIO], mode: str = "rt"
- ) -> None:
+ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None:
# Not possible with the type signature, but be verbose for user friendliness
if file is None:
raise ValueError("The given file cannot be None")
diff --git a/can/io/logger.py b/can/io/logger.py
index 3890e7432..6c3933294 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -15,7 +15,7 @@
)
from types import TracebackType
-
+from typing_extensions import Literal
from pkg_resources import iter_entry_points
from ..message import Message
@@ -95,6 +95,9 @@ def __new__( # type: ignore
f'No write support for this unknown log format "{suffix}"'
) from None
+ def on_message_received(self, msg: Message) -> None:
+ pass
+
class BaseRotatingLogger(Listener, BaseIOHandler, ABC):
"""
@@ -232,7 +235,7 @@ def __exit__(
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
- ) -> bool:
+ ) -> Literal[False]:
return self._writer.__exit__(exc_type, exc_val, exc_tb)
@abstractmethod
diff --git a/can/io/printer.py b/can/io/printer.py
index ec003e0eb..09c86f81f 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -4,7 +4,7 @@
import logging
-from typing import Optional
+from typing import Optional, cast, TextIO
from ..message import Message
from ..listener import Listener
@@ -24,6 +24,8 @@ class Printer(BaseIOHandler, Listener):
standard out
"""
+ file: Optional[TextIO]
+
def __init__(
self, file: Optional[AcceptedIOType] = None, append: bool = False
) -> None:
@@ -35,12 +37,11 @@ def __init__(
:param append: If set to `True` messages, are appended to the file,
else the file is truncated
"""
- self.write_to_file = file is not None
mode = "a" if append else "w"
super().__init__(file, mode=mode)
def on_message_received(self, msg: Message) -> None:
- if self.write_to_file:
+ if self.file is not None:
self.file.write(str(msg) + "\n")
else:
print(msg)
diff --git a/can/typechecking.py b/can/typechecking.py
index 627e1a86a..3e3cca833 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -27,7 +27,7 @@
Channel = typing.Union[ChannelInt, ChannelStr]
# Used by the IO module
-FileLike = typing.IO[typing.Any]
+FileLike = typing.Union[typing.TextIO, typing.BinaryIO]
StringPathLike = typing.Union[str, "os.PathLike[str]"]
AcceptedIOType = typing.Union[FileLike, StringPathLike]
From b75e214d6d266566094e4164b8294f06a6b949a4 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 20 Jan 2022 16:54:26 +0100
Subject: [PATCH 055/475] add newline at end
---
setup.cfg | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.cfg b/setup.cfg
index 48a688026..068badd4c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,4 +9,4 @@ no_implicit_optional = True
disallow_incomplete_defs = True
warn_redundant_casts = True
warn_unused_ignores = True
-exclude = (^venv|^test|^can/interfaces|^setup.py$)
\ No newline at end of file
+exclude = (^venv|^test|^can/interfaces|^setup.py$)
From 53e5e116177b3a4751d900fc4a6a1170fea20163 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 21 Jan 2022 10:15:06 +0100
Subject: [PATCH 056/475] Add Github Actions badge
---
README.rst | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.rst b/README.rst
index 84c270e0f..5eec029b2 100644
--- a/README.rst
+++ b/README.rst
@@ -3,7 +3,7 @@ python-can
|release| |python_implementation| |downloads| |downloads_monthly| |formatter|
-|docs| |build_travis| |coverage| |mergify|
+|docs| |github-actions| |build_travis| |coverage| |mergify|
.. |release| image:: https://img.shields.io/pypi/v/python-can.svg
:target: https://pypi.python.org/pypi/python-can/
@@ -29,6 +29,10 @@ python-can
:target: https://python-can.readthedocs.io/en/stable/
:alt: Documentation
+.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/build.yml/badge.svg?branch=develop
+ :target: https://github.com/hardbyte/python-can/actions/workflows/build.yml
+ :alt: Github Actions workflow status
+
.. |build_travis| image:: https://img.shields.io/travis/com/hardbyte/python-can/develop.svg?label=Travis%20CI
:target: https://travis-ci.com/hardbyte/python-can
:alt: Travis CI Server for develop branch
From b66ea80d567892c7475192af9d51c71d35b39efd Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 21 Jan 2022 10:27:11 +0100
Subject: [PATCH 057/475] remove continue-on-error so workflow fails
---
.github/workflows/build.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1f57103a5..993247868 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,7 +41,6 @@ jobs:
static-code-analysis:
runs-on: ubuntu-latest
- continue-on-error: true
steps:
- uses: actions/checkout@v2
- name: Set up Python
From 88cb3bbe38ffd6a989bde207f1aaeedc49f8090a Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 21 Jan 2022 10:38:17 +0100
Subject: [PATCH 058/475] improve ASCReader robustness
---
can/io/asc.py | 19 ++++++++++++-------
test/data/logfile.asc | 1 +
test/logformats_test.py | 3 +++
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/can/io/asc.py b/can/io/asc.py
index 74f630f43..5e59483c5 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -6,7 +6,8 @@
- under `test/data/logfile.asc`
"""
import gzip
-from typing import cast, Any, Generator, IO, List, Optional, Dict, Union, TextIO
+import re
+from typing import Any, Generator, List, Optional, Dict, Union, TextIO
from datetime import datetime
import time
@@ -85,8 +86,8 @@ def _extract_header(self):
self.timestamps_format = timestamp_format
elif lower_case.endswith("internal events logged"):
self.internal_events_logged = not lower_case.startswith("no")
- elif lower_case.startswith("// version"):
- # the test files include `// version 9.0.0` - not sure what this is
+ elif lower_case.startswith("//"):
+ # ignore comments
continue
# grab absolute timestamp
elif lower_case.startswith("begin triggerblock"):
@@ -210,14 +211,18 @@ def __iter__(self) -> Generator[Message, None, None]:
self._extract_header()
for line in self.file:
- temp = line.strip()
- if not temp or not temp[0].isdigit():
- # Could be a comment
+ line = line.strip()
+
+ if not re.match(
+ r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)",
+ line,
+ re.ASCII | re.IGNORECASE,
+ ):
continue
msg_kwargs: Dict[str, Union[float, bool, int]] = {}
try:
- _timestamp, channel, rest_of_message = temp.split(None, 2)
+ _timestamp, channel, rest_of_message = line.split(None, 2)
timestamp = float(_timestamp) + self.start_time
msg_kwargs["timestamp"] = timestamp
if channel == "CANFD":
diff --git a/test/data/logfile.asc b/test/data/logfile.asc
index 77ebdb78a..8e6ac0464 100644
--- a/test/data/logfile.asc
+++ b/test/data/logfile.asc
@@ -2,6 +2,7 @@ date Sam Sep 30 15:06:13.191 2017
base hex timestamps absolute
internal events logged
// version 9.0.0
+//0.000000 previous log file: logfile_errorframes.asc
Begin Triggerblock Sam Sep 30 15:06:13.191 2017
0.000000 Start of measurement
0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 400bf369d..a7417fa5d 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -554,6 +554,9 @@ def test_can_and_canfd_error_frames(self):
actual = self._read_log_file("test_CanErrorFrames.asc")
self.assertMessagesEqual(actual, expected_messages)
+ def test_ignore_comments(self):
+ _msg_list = self._read_log_file("logfile.asc")
+
class TestGzipASCFileFormat(ReaderWriterTest):
"""Tests can.GzipASCWriter and can.GzipASCReader"""
From 2bc44be4da22089ed779c8c3c9990516bacf54ef Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 21 Jan 2022 11:39:13 +0100
Subject: [PATCH 059/475] add comment
---
can/io/asc.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/can/io/asc.py b/can/io/asc.py
index 5e59483c5..db8f66358 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -218,6 +218,8 @@ def __iter__(self) -> Generator[Message, None, None]:
line,
re.ASCII | re.IGNORECASE,
):
+ # line might be a comment, chip status,
+ # J1939 message or some other unsupported event
continue
msg_kwargs: Dict[str, Union[float, bool, int]] = {}
From 57bc62c58f2e8968c84ecfb33ae1c8e8fa87070d Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 14 Jan 2022 16:03:35 +0100
Subject: [PATCH 060/475] change DLC to DL
---
can/message.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/message.py b/can/message.py
index 854d211a1..87cb6a199 100644
--- a/can/message.py
+++ b/can/message.py
@@ -130,7 +130,7 @@ def __str__(self) -> str:
field_strings.append(flag_string)
- field_strings.append(f"DLC: {self.dlc:2d}")
+ field_strings.append(f"DL: {self.dlc:2d}")
data_strings = []
if self.data is not None:
for index in range(0, min(self.dlc, len(self.data))):
From 30c421a625436c7c7e07949bec9633eddb6c5dbb Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 21 Jan 2022 12:56:36 +0100
Subject: [PATCH 061/475] update CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index da451f157..d1428075d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -138,6 +138,7 @@ Other API changes and improvements
* Speed up interface plugin imports by removing pkg_resources (#1110)
* Allowing for extra config arguments in can.logger (#1142, #1170)
* Add changed byte highlighting to viewer.py (#1159)
+* Change DLC to DL in Message.\_\_str\_\_() (#1212)
Other Bugfixes
--------------
From 2185f27977e19b9914b7b49f3ee620b2cd6f011e Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sun, 23 Jan 2022 09:20:24 +0100
Subject: [PATCH 062/475] Moderinze README code
---
README.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.rst b/README.rst
index 5eec029b2..956033de1 100644
--- a/README.rst
+++ b/README.rst
@@ -90,7 +90,7 @@ Example usage
import can
# create a bus instance
- # many other interfaces are supported as well (see below)
+ # many other interfaces are supported as well (see documentation)
bus = can.Bus(interface='socketcan',
channel='vcan0',
receive_own_messages=True)
@@ -102,7 +102,7 @@ Example usage
# iterate over received messages
for msg in bus:
- print("{:X}: {}".format(msg.arbitration_id, msg.data))
+ print(f"{msg.arbitration_id:X}: {msg.data}")
# or use an asynchronous notifier
notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()])
From 5088ec2d07f13894b67c327be6b5d4b4ed0c4f17 Mon Sep 17 00:00:00 2001
From: TJ
Date: Mon, 17 Jan 2022 18:14:56 -0800
Subject: [PATCH 063/475] Add unzip method to logreader
---
can/io/player.py | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/can/io/player.py b/can/io/player.py
index f710e15d5..6712089dd 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -3,7 +3,7 @@
well as :class:`MessageSync` which plays back messages
in the recorded order an time intervals.
"""
-
+import gzip
import pathlib
from time import time, sleep
import typing
@@ -14,7 +14,7 @@
import can
from .generic import BaseIOHandler, MessageReader
-from .asc import ASCReader, GzipASCReader
+from .asc import ASCReader
from .blf import BLFReader
from .canutils import CanutilsLogReader
from .csv import CSVReader
@@ -27,12 +27,13 @@ class LogReader(BaseIOHandler):
The format is determined from the file format which can be one of:
* .asc
- * .asc.gz
* .blf
* .csv
* .db
* .log
+ Or any of the above compressed using gzip (.gz)
+
Exposes a simple iterator interface, to use simply:
>>> for msg in LogReader("some/path/to/my_file.log"):
@@ -50,7 +51,6 @@ class LogReader(BaseIOHandler):
fetched_plugins = False
message_readers = {
".asc": ASCReader,
- ".asc.gz": GzipASCReader,
".blf": BLFReader,
".csv": CSVReader,
".db": SqliteReader,
@@ -77,7 +77,11 @@ def __new__( # type: ignore
)
LogReader.fetched_plugins = True
- suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes)
+ suffix = pathlib.PurePath(filename).suffix.lower()
+
+ if suffix == ".gz":
+ suffix, filename = LogReader.unzip(filename)
+
try:
return typing.cast(
MessageReader,
@@ -88,6 +92,23 @@ def __new__( # type: ignore
f'No read support for this unknown log format "{suffix}"'
) from None
+ @staticmethod
+ def unzip(zipfile: "can.typechecking.StringPathLike"):
+ """
+ Return the suffix and io object of the decompressed file.
+ """
+ real_suffix = pathlib.Path(zipfile).suffixes[-2].lower()
+ file = gzip.open(zipfile, "rt")
+
+ # Re-open in binary mode if file not readable.
+ try:
+ file.read()
+ file.seek(0)
+ except UnicodeDecodeError:
+ file = gzip.open(zipfile, "rb")
+
+ return real_suffix, file
+
class MessageSync: # pylint: disable=too-few-public-methods
"""
From 7e59007fb8e3aa9d07bec105ad0a7b42e9093f9f Mon Sep 17 00:00:00 2001
From: Teejay
Date: Thu, 20 Jan 2022 20:16:54 -0800
Subject: [PATCH 064/475] Apply PR feedback
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Rename method to decompress
Add protection against memory error
---
can/io/player.py | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
diff --git a/can/io/player.py b/can/io/player.py
index 6712089dd..96d3a5b7c 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -80,7 +80,7 @@ def __new__( # type: ignore
suffix = pathlib.PurePath(filename).suffix.lower()
if suffix == ".gz":
- suffix, filename = LogReader.unzip(filename)
+ suffix, filename = LogReader.decompress(filename)
try:
return typing.cast(
@@ -93,21 +93,16 @@ def __new__( # type: ignore
) from None
@staticmethod
- def unzip(zipfile: "can.typechecking.StringPathLike"):
+ def decompress(
+ filename: "can.typechecking.StringPathLike",
+ ) -> typing.Tuple[str, typing.IO[typing.Any]]:
"""
Return the suffix and io object of the decompressed file.
"""
- real_suffix = pathlib.Path(zipfile).suffixes[-2].lower()
- file = gzip.open(zipfile, "rt")
-
- # Re-open in binary mode if file not readable.
- try:
- file.read()
- file.seek(0)
- except UnicodeDecodeError:
- file = gzip.open(zipfile, "rb")
+ real_suffix = pathlib.Path(filename).suffixes[-2].lower()
+ mode = "rb" if real_suffix == ".blf" else "rt"
- return real_suffix, file
+ return real_suffix, gzip.open(filename, mode)
class MessageSync: # pylint: disable=too-few-public-methods
From 56c579159b07cca7b3da203141ff2c65876ab759 Mon Sep 17 00:00:00 2001
From: TJ
Date: Thu, 20 Jan 2022 20:59:09 -0800
Subject: [PATCH 065/475] Add player unittest
---
test/data/test_CanMessage.asc.gz | Bin 0 -> 277 bytes
test/test_player.py | 14 +++++++++++---
2 files changed, 11 insertions(+), 3 deletions(-)
create mode 100644 test/data/test_CanMessage.asc.gz
diff --git a/test/data/test_CanMessage.asc.gz b/test/data/test_CanMessage.asc.gz
new file mode 100644
index 0000000000000000000000000000000000000000..25375d1b80fcf39afe18534e6fd792b4ee4cc2e0
GIT binary patch
literal 277
zcmV+w0qXuAiwFpT3*}(|19W9`bYDYZZcSx#b75y?E@5+H09}vWYQr!LM(^_!-xH*A
z{!{2pvJD0sjHNH&ELs|t*u{3r9)HTlN;hGIuyi=mpaY}R3pzd{C8&H)#a^YcsudDa
zWz=iWIPoVCriFdb%h^Ns-p*^_XflDF(KGnMCV0$t9C?U#J6zcL$r{u##S}F>P6kIe
zN!#_aeftn=xLWDP`ttlE1|Z~jpbOds*mo?f{pxrT*){+qaZZYmNZD4njiaoL0TrkQ
zNiIPJCy;O#g>3Qf;fc){PB None:
# Patch VirtualBus object
patcher_virtual_bus = mock.patch("can.interfaces.virtual.VirtualBus", spec=True)
@@ -29,9 +32,6 @@ def setUp(self) -> None:
self.addCleanup(patcher_sleep.stop)
self.baseargs = [sys.argv[0], "-i", "virtual"]
- self.logfile = os.path.join(
- os.path.dirname(__file__), "data", "test_CanMessage.asc"
- )
def assertSuccessfulCleanup(self):
self.MockVirtualBus.assert_called_once()
@@ -111,5 +111,13 @@ def test_play_error_frame(self):
self.assertSuccessfulCleanup()
+class TestPlayerCompressedFile(TestPlayerScriptModule):
+ """
+ Re-run tests using a compressed file.
+ """
+
+ logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc.gz")
+
+
if __name__ == "__main__":
unittest.main()
From 5e5ca2b75455ed3d1d5c9d8803ace069fb0886e6 Mon Sep 17 00:00:00 2001
From: TJ
Date: Sat, 22 Jan 2022 12:25:26 -0800
Subject: [PATCH 066/475] Add compress method to logger
---
can/io/logger.py | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index 3890e7432..8dbd14f32 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -6,12 +6,15 @@
import pathlib
from abc import ABC, abstractmethod
from datetime import datetime
+import gzip
from typing import (
Any,
Optional,
Callable,
cast,
+ IO,
Type,
+ Tuple,
)
from types import TracebackType
@@ -21,7 +24,7 @@
from ..message import Message
from ..listener import Listener
from .generic import BaseIOHandler, FileIOMessageWriter
-from .asc import ASCWriter, GzipASCWriter
+from .asc import ASCWriter
from .blf import BLFWriter
from .canutils import CanutilsLogWriter
from .csv import CSVWriter
@@ -36,13 +39,14 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
The format is determined from the file format which can be one of:
* .asc: :class:`can.ASCWriter`
- * .asc.gz: :class:`can.CompressedASCWriter`
* .blf :class:`can.BLFWriter`
* .csv: :class:`can.CSVWriter`
* .db: :class:`can.SqliteWriter`
* .log :class:`can.CanutilsLogWriter`
* .txt :class:`can.Printer`
+ Or any of the above compressed using gzip (.gz)
+
The **filename** may also be *None*, to fall back to :class:`can.Printer`.
The log files may be incomplete until `stop()` is called due to buffering.
@@ -55,7 +59,6 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
fetched_plugins = False
message_writers = {
".asc": ASCWriter,
- ".asc.gz": GzipASCWriter,
".blf": BLFWriter,
".csv": CSVWriter,
".db": SqliteWriter,
@@ -85,7 +88,11 @@ def __new__( # type: ignore
)
Logger.fetched_plugins = True
- suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes)
+ suffix = pathlib.PurePath(filename).suffix.lower()
+
+ if suffix == ".gz":
+ suffix, filename = Logger.compress(filename)
+
try:
return cast(
Listener, Logger.message_writers[suffix](filename, *args, **kwargs)
@@ -95,6 +102,17 @@ def __new__( # type: ignore
f'No write support for this unknown log format "{suffix}"'
) from None
+ @staticmethod
+ def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]:
+ """
+ Return the suffix and io object of the decompressed file.
+ File will automatically recompress upon close.
+ """
+ real_suffix = pathlib.Path(filename).suffixes[-2].lower()
+ mode = "ab" if real_suffix == ".blf" else "at"
+
+ return real_suffix, gzip.open(filename, mode)
+
class BaseRotatingLogger(Listener, BaseIOHandler, ABC):
"""
From c56ff406531d9ef0d3a469a0be482d05784a82b7 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 25 Jan 2022 18:39:36 +0100
Subject: [PATCH 067/475] check vector with mypy
---
can/interfaces/vector/canlib.py | 121 ++++++++++++++++--------------
can/interfaces/vector/xldriver.py | 1 +
setup.cfg | 23 +++++-
test/test_vector.py | 10 +--
4 files changed, 92 insertions(+), 63 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 1313b00b7..4d81e8299 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -10,7 +10,8 @@
import logging
import time
import os
-from typing import List, NamedTuple, Optional, Tuple, Sequence, Union
+from types import ModuleType
+from typing import List, NamedTuple, Optional, Tuple, Sequence, Union, Any, Dict
try:
# Try builtin Python 3 Windows API
@@ -18,14 +19,7 @@
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
+ HAS_EVENTS = False
# Import Modules
# ==============
@@ -36,7 +30,7 @@
deprecated_args_alias,
time_perfcounter_correlation,
)
-from can.typechecking import AutoDetectedConfig, CanFilters
+from can.typechecking import AutoDetectedConfig, CanFilters, Channel
# Define Module Logger
# ====================
@@ -48,7 +42,7 @@
from . import xldefine, xlclass
# Import safely Vector API module for Travis tests
-xldriver = None
+xldriver: Optional[ModuleType] = None
try:
from . import xldriver
except Exception as exc:
@@ -73,20 +67,20 @@ def __init__(
channel: Union[int, Sequence[int], str],
can_filters: Optional[CanFilters] = None,
poll_interval: float = 0.01,
- receive_own_messages: bool = False,
- bitrate: int = None,
+ receive_own_messages: Optional[bool] = False,
+ bitrate: Optional[int] = None,
rx_queue_size: int = 2 ** 14,
- app_name: str = "CANalyzer",
- serial: int = None,
- fd: bool = False,
- data_bitrate: int = None,
+ app_name: Optional[str] = "CANalyzer",
+ serial: Optional[int] = None,
+ fd: Optional[bool] = False,
+ data_bitrate: Optional[int] = None,
sjw_abr: int = 2,
tseg1_abr: int = 6,
tseg2_abr: int = 3,
sjw_dbr: int = 2,
tseg1_dbr: int = 6,
tseg2_dbr: int = 3,
- **kwargs,
+ **kwargs: Any,
) -> None:
"""
:param channel:
@@ -144,16 +138,18 @@ def __init__(
if xldriver is None:
raise CanInterfaceNotImplementedError("The Vector API has not been loaded")
+ self.xldriver = xldriver # keep reference so mypy knows it is not None
self.poll_interval = poll_interval
- if isinstance(channel, str): # must be checked before generic Sequence
+ self.channels: Sequence[int]
+ if isinstance(channel, int):
+ self.channels = [channel]
+ elif isinstance(channel, str): # must be checked before generic Sequence
# Assume comma separated string of channels
self.channels = [int(ch.strip()) for ch in channel.split(",")]
- elif isinstance(channel, int):
- self.channels = [channel]
elif isinstance(channel, Sequence):
- self.channels = channel
+ self.channels = [int(ch) for ch in channel]
else:
raise TypeError(
f"Invalid type for channels parameter: {type(channel).__name__}"
@@ -185,12 +181,12 @@ def __init__(
"None of the configured channels could be found on the specified hardware."
)
- xldriver.xlOpenDriver()
+ self.xldriver.xlOpenDriver()
self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE)
self.mask = 0
self.fd = fd
# Get channels masks
- self.channel_masks = {}
+ self.channel_masks: Dict[Optional[Channel], int] = {}
self.index_to_channel = {}
for channel in self.channels:
@@ -200,7 +196,7 @@ def __init__(
app_name, channel
)
LOG.debug("Channel index %d found", channel)
- idx = xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel)
+ idx = self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel)
if idx < 0:
# Undocumented behavior! See issue #353.
# If hardware is unavailable, this function returns -1.
@@ -224,7 +220,7 @@ def __init__(
if bitrate or fd:
permission_mask.value = self.mask
if fd:
- xldriver.xlOpenPort(
+ self.xldriver.xlOpenPort(
self.port_handle,
self._app_name,
self.mask,
@@ -234,7 +230,7 @@ def __init__(
xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
)
else:
- xldriver.xlOpenPort(
+ self.xldriver.xlOpenPort(
self.port_handle,
self._app_name,
self.mask,
@@ -267,7 +263,7 @@ def __init__(
self.canFdConf.tseg1Dbr = int(tseg1_dbr)
self.canFdConf.tseg2Dbr = int(tseg2_dbr)
- xldriver.xlCanFdSetConfiguration(
+ self.xldriver.xlCanFdSetConfiguration(
self.port_handle, self.mask, self.canFdConf
)
LOG.info(
@@ -289,7 +285,7 @@ def __init__(
)
else:
if bitrate:
- xldriver.xlCanSetChannelBitrate(
+ self.xldriver.xlCanSetChannelBitrate(
self.port_handle, permission_mask, bitrate
)
LOG.info("SetChannelBitrate: baudr.=%u", bitrate)
@@ -298,16 +294,16 @@ def __init__(
# Enable/disable TX receipts
tx_receipts = 1 if receive_own_messages else 0
- xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0)
+ self.xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0)
if HAS_EVENTS:
self.event_handle = xlclass.XLhandle()
- xldriver.xlSetNotification(self.port_handle, self.event_handle, 1)
+ self.xldriver.xlSetNotification(self.port_handle, self.event_handle, 1)
else:
LOG.info("Install pywin32 to avoid polling")
try:
- xldriver.xlActivateChannel(
+ self.xldriver.xlActivateChannel(
self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0
)
except VectorOperationError as error:
@@ -320,17 +316,17 @@ def __init__(
if time.get_clock_info("time").resolution > 1e-5:
ts, perfcounter = time_perfcounter_correlation()
try:
- xldriver.xlGetSyncTime(self.port_handle, offset)
+ self.xldriver.xlGetSyncTime(self.port_handle, offset)
except VectorInitializationError:
- xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
+ self.xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
current_perfcounter = time.perf_counter()
now = ts + (current_perfcounter - perfcounter)
self._time_offset = now - offset.value * 1e-9
else:
try:
- xldriver.xlGetSyncTime(self.port_handle, offset)
+ self.xldriver.xlGetSyncTime(self.port_handle, offset)
except VectorInitializationError:
- xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
+ self.xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
self._time_offset = time.time() - offset.value * 1e-9
except VectorInitializationError:
@@ -339,7 +335,7 @@ def __init__(
self._is_filtered = False
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
- def _apply_filters(self, filters) -> None:
+ def _apply_filters(self, filters: Optional[CanFilters]) -> None:
if filters:
# Only up to one filter per ID type allowed
if len(filters) == 1 or (
@@ -348,7 +344,7 @@ def _apply_filters(self, filters) -> None:
):
try:
for can_filter in filters:
- xldriver.xlCanSetChannelAcceptance(
+ self.xldriver.xlCanSetChannelAcceptance(
self.port_handle,
self.mask,
can_filter["can_id"],
@@ -370,14 +366,14 @@ def _apply_filters(self, filters) -> None:
# fallback: reset filters
self._is_filtered = False
try:
- xldriver.xlCanSetChannelAcceptance(
+ self.xldriver.xlCanSetChannelAcceptance(
self.port_handle,
self.mask,
0x0,
0x0,
xldefine.XL_AcceptanceFilter.XL_CAN_EXT,
)
- xldriver.xlCanSetChannelAcceptance(
+ self.xldriver.xlCanSetChannelAcceptance(
self.port_handle,
self.mask,
0x0,
@@ -417,14 +413,14 @@ def _recv_internal(
else:
time_left = end_time - time.time()
time_left_ms = max(0, int(time_left * 1000))
- WaitForSingleObject(self.event_handle.value, time_left_ms)
+ WaitForSingleObject(self.event_handle.value, time_left_ms) # type: ignore
else:
# Wait a short time until we try again
time.sleep(self.poll_interval)
def _recv_canfd(self) -> Optional[Message]:
xl_can_rx_event = xlclass.XLcanRxEvent()
- xldriver.xlCanReceive(self.port_handle, xl_can_rx_event)
+ self.xldriver.xlCanReceive(self.port_handle, xl_can_rx_event)
if xl_can_rx_event.tag == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK:
is_rx = True
@@ -470,7 +466,7 @@ def _recv_canfd(self) -> Optional[Message]:
def _recv_can(self) -> Optional[Message]:
xl_event = xlclass.XLevent()
event_count = ctypes.c_uint(1)
- xldriver.xlReceive(self.port_handle, event_count, xl_event)
+ self.xldriver.xlReceive(self.port_handle, event_count, xl_event)
if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG:
self.handle_can_event(xl_event)
@@ -523,7 +519,7 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None:
`XL_CAN_EV_TAG_TX_ERROR`, `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag.
"""
- def send(self, msg: Message, timeout: Optional[float] = None):
+ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
self._send_sequence([msg])
def _send_sequence(self, msgs: Sequence[Message]) -> int:
@@ -548,7 +544,9 @@ def _send_can_msg_sequence(self, msgs: Sequence[Message]) -> int:
*map(self._build_xl_event, msgs)
)
- xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event_array)
+ self.xldriver.xlCanTransmit(
+ self.port_handle, mask, message_count, xl_event_array
+ )
return message_count.value
@staticmethod
@@ -580,7 +578,7 @@ def _send_can_fd_msg_sequence(self, msgs: Sequence[Message]) -> int:
)
msg_count_sent = ctypes.c_uint(0)
- xldriver.xlCanTransmitEx(
+ self.xldriver.xlCanTransmitEx(
self.port_handle, mask, message_count, msg_count_sent, xl_can_tx_event_array
)
return msg_count_sent.value
@@ -611,17 +609,17 @@ def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent:
return xl_can_tx_event
def flush_tx_buffer(self) -> None:
- xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask)
+ self.xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask)
def shutdown(self) -> None:
super().shutdown()
- xldriver.xlDeactivateChannel(self.port_handle, self.mask)
- xldriver.xlClosePort(self.port_handle)
- xldriver.xlCloseDriver()
+ self.xldriver.xlDeactivateChannel(self.port_handle, self.mask)
+ self.xldriver.xlClosePort(self.port_handle)
+ self.xldriver.xlCloseDriver()
def reset(self) -> None:
- xldriver.xlDeactivateChannel(self.port_handle, self.mask)
- xldriver.xlActivateChannel(
+ self.xldriver.xlDeactivateChannel(self.port_handle, self.mask)
+ self.xldriver.xlActivateChannel(
self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0
)
@@ -657,7 +655,7 @@ def _detect_available_configs() -> List[AutoDetectedConfig]:
"vector_channel_config": channel_config,
}
)
- return configs
+ return configs # type: ignore
@staticmethod
def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None:
@@ -666,6 +664,9 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None:
:param wait_for_finish:
Time to wait for user input in milliseconds.
"""
+ if xldriver is None:
+ raise CanInterfaceNotImplementedError("The Vector API has not been loaded")
+
xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish))
@staticmethod
@@ -685,14 +686,17 @@ def get_application_config(
:raises can.interfaces.vector.VectorInitializationError:
If the application name does not exist in the Vector hardware configuration.
"""
+ if xldriver is None:
+ raise CanInterfaceNotImplementedError("The Vector API has not been loaded")
+
hw_type = ctypes.c_uint()
hw_index = ctypes.c_uint()
hw_channel = ctypes.c_uint()
- app_channel = ctypes.c_uint(app_channel)
+ _app_channel = ctypes.c_uint(app_channel)
xldriver.xlGetApplConfig(
app_name.encode(),
- app_channel,
+ _app_channel,
hw_type,
hw_index,
hw_channel,
@@ -707,7 +711,7 @@ def set_application_config(
hw_type: xldefine.XL_HardwareType,
hw_index: int,
hw_channel: int,
- **kwargs,
+ **kwargs: Any,
) -> None:
"""Modify the application settings in Vector Hardware Configuration.
@@ -737,6 +741,9 @@ def set_application_config(
:raises can.interfaces.vector.VectorInitializationError:
If the application name does not exist in the Vector hardware configuration.
"""
+ if xldriver is None:
+ raise CanInterfaceNotImplementedError("The Vector API has not been loaded")
+
xldriver.xlSetApplConfig(
app_name.encode(),
app_channel,
@@ -758,7 +765,7 @@ def set_timer_rate(self, timer_rate_ms: int) -> None:
the timer events.
"""
timer_rate_10us = timer_rate_ms * 100
- xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us)
+ self.xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us)
class VectorChannelConfig(NamedTuple):
diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py
index db57d5911..3243fa4a0 100644
--- a/can/interfaces/vector/xldriver.py
+++ b/can/interfaces/vector/xldriver.py
@@ -1,3 +1,4 @@
+# type: ignore
"""
Ctypes wrapper module for Vector CAN Interface on win32/win64 systems.
diff --git a/setup.cfg b/setup.cfg
index 068badd4c..7df42541f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,4 +9,25 @@ no_implicit_optional = True
disallow_incomplete_defs = True
warn_redundant_casts = True
warn_unused_ignores = True
-exclude = (^venv|^test|^can/interfaces|^setup.py$)
+exclude =
+ (?x)(
+ venv
+ |^test
+ |^setup.py$
+ |^can/interfaces/etas
+ |^can/interfaces/gs_usb
+ |^can/interfaces/ics_neovi
+ |^can/interfaces/iscan
+ |^can/interfaces/ixxat
+ |^can/interfaces/kvaser
+ |^can/interfaces/nican
+ |^can/interfaces/neousys
+ |^can/interfaces/pcan
+ |^can/interfaces/serial
+ |^can/interfaces/slcan
+ |^can/interfaces/socketcan
+ |^can/interfaces/systec
+ |^can/interfaces/udp_multicast
+ |^can/interfaces/usb2can
+ |^can/interfaces/virtual
+ )
diff --git a/test/test_vector.py b/test/test_vector.py
index b1626b18c..800b8614c 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -309,7 +309,7 @@ def test_vector_error_pickle(self) -> None:
with pytest.raises(error_type):
raise exc_unpickled
- def test_vector_subteype_error_from_generic(self) -> None:
+ def test_vector_subtype_error_from_generic(self) -> None:
for error_type in [VectorInitializationError, VectorOperationError]:
with self.subTest(f"error_type = {error_type.__name__}"):
@@ -320,13 +320,13 @@ def test_vector_subteype_error_from_generic(self) -> None:
generic = VectorError(error_code, error_string, function)
# pickle and unpickle
- specififc: VectorError = error_type.from_generic(generic)
+ specific: VectorError = error_type.from_generic(generic)
- self.assertEqual(str(generic), str(specififc))
- self.assertEqual(error_code, specififc.error_code)
+ self.assertEqual(str(generic), str(specific))
+ self.assertEqual(error_code, specific.error_code)
with pytest.raises(error_type):
- raise specififc
+ raise specific
class TestVectorChannelConfig:
From 473939121442f88daf8c48abffe4c13d92004a33 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 25 Jan 2022 23:02:01 +0100
Subject: [PATCH 068/475] add test and correct type annotations
---
can/interfaces/vector/canlib.py | 5 +++--
setup.cfg | 1 +
test/test_vector.py | 6 ++++++
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 4d81e8299..c3bc5413b 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -19,6 +19,7 @@
HAS_EVENTS = True
except ImportError:
+ WaitForSingleObject, INFINITE = None, None
HAS_EVENTS = False
# Import Modules
@@ -67,12 +68,12 @@ def __init__(
channel: Union[int, Sequence[int], str],
can_filters: Optional[CanFilters] = None,
poll_interval: float = 0.01,
- receive_own_messages: Optional[bool] = False,
+ receive_own_messages: bool = False,
bitrate: Optional[int] = None,
rx_queue_size: int = 2 ** 14,
app_name: Optional[str] = "CANalyzer",
serial: Optional[int] = None,
- fd: Optional[bool] = False,
+ fd: bool = False,
data_bitrate: Optional[int] = None,
sjw_abr: int = 2,
tseg1_abr: int = 6,
diff --git a/setup.cfg b/setup.cfg
index 7df42541f..b402ee645 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,6 +14,7 @@ exclude =
venv
|^test
|^setup.py$
+ |^can/interfaces/__init__.py
|^can/interfaces/etas
|^can/interfaces/gs_usb
|^can/interfaces/ics_neovi
diff --git a/test/test_vector.py b/test/test_vector.py
index 800b8614c..338783136 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -22,6 +22,7 @@
VectorOperationError,
VectorChannelConfig,
)
+from test.config import IS_WINDOWS
class TestVectorBus(unittest.TestCase):
@@ -328,6 +329,11 @@ def test_vector_subtype_error_from_generic(self) -> None:
with pytest.raises(error_type):
raise specific
+ @unittest.skipUnless(IS_WINDOWS, "Windows specific test")
+ def test_winapi_availability(self) -> None:
+ self.assertIsNotNone(canlib.WaitForSingleObject)
+ self.assertIsNotNone(canlib.INFINITE)
+
class TestVectorChannelConfig:
def test_attributes(self):
From 42348084febb6d9784b0772bf422ae56edf5af1f Mon Sep 17 00:00:00 2001
From: TJ
Date: Sat, 22 Jan 2022 13:18:09 -0800
Subject: [PATCH 069/475] Add logger unittest
---
test/test_logger.py | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/test/test_logger.py b/test/test_logger.py
index a046b919f..6a4472f43 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -8,7 +8,9 @@
import unittest
from unittest import mock
from unittest.mock import Mock
+import gzip
import sys
+import tempfile
import can
import can.logger
@@ -105,5 +107,38 @@ def test_log_virtual_sizedlogger(self):
self.mock_logger_sized.assert_called_once()
+class TestLoggerCompressedFile(unittest.TestCase):
+ def setUp(self) -> None:
+ # Patch VirtualBus object
+ self.patcher_virtual_bus = mock.patch(
+ "can.interfaces.virtual.VirtualBus", spec=True
+ )
+ self.MockVirtualBus = self.patcher_virtual_bus.start()
+ self.mock_virtual_bus = self.MockVirtualBus.return_value
+
+ self.testmsg = can.Message(
+ arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True
+ )
+
+ self.baseargs = [sys.argv[0], "-i", "virtual"]
+
+ def test_compressed_logfile(self):
+ """
+ Basic test to verify Logger is able to write gzip files.
+ """
+ self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt])
+
+ with tempfile.NamedTemporaryFile(suffix=".log.gz", delete=True) as compressed:
+ sys.argv = self.baseargs + ["--file_name", compressed.name]
+ can.logger.main()
+ with gzip.open(compressed.name, "rt") as decompressed:
+ last_line = decompressed.readlines()[-1]
+
+ self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n")
+
+ def tearDown(self) -> None:
+ self.patcher_virtual_bus.stop()
+
+
if __name__ == "__main__":
unittest.main()
From 57a955e06ac1e6077d998003b57bc9274f8c7d35 Mon Sep 17 00:00:00 2001
From: TJ
Date: Sun, 23 Jan 2022 22:14:27 -0800
Subject: [PATCH 070/475] Add multiple dot suffix variant
---
test/logformats_test.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 400bf369d..669567e47 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -48,6 +48,7 @@ def test_extension_matching(self):
suffix_variants = [
suffix.upper(),
suffix.lower(),
+ f"can.msg.ext{suffix}",
"".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]),
]
for suffix_variant in suffix_variants:
From a2b840d63f525bd375200beb43785d62b72c85f6 Mon Sep 17 00:00:00 2001
From: Tbruno25
Date: Wed, 26 Jan 2022 04:43:14 +0000
Subject: [PATCH 071/475] Format code with black
---
can/io/logger.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index 15e8bc0c5..8f2611794 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -102,7 +102,6 @@ def __new__( # type: ignore
f'No write support for this unknown log format "{suffix}"'
) from None
-
@staticmethod
def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]:
"""
@@ -114,7 +113,6 @@ def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]:
return real_suffix, gzip.open(filename, mode)
-
def on_message_received(self, msg: Message) -> None:
pass
From ac1df031cfc9a7e597cd3c4fc504b001ef9df394 Mon Sep 17 00:00:00 2001
From: TJ
Date: Tue, 25 Jan 2022 20:51:00 -0800
Subject: [PATCH 072/475] Remove gzip classes
---
can/__init__.py | 2 +-
can/io/__init__.py | 2 +-
can/io/asc.py | 67 ----------------------------------------------
3 files changed, 2 insertions(+), 69 deletions(-)
diff --git a/can/__init__.py b/can/__init__.py
index c95b19ebf..618ef347f 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -25,7 +25,7 @@
)
from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync
-from .io import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader
+from .io import ASCWriter, ASCReader
from .io import BLFReader, BLFWriter
from .io import CanutilsLogReader, CanutilsLogWriter
from .io import CSVWriter, CSVReader
diff --git a/can/io/__init__.py b/can/io/__init__.py
index 66e3a8c56..0d3741b05 100644
--- a/can/io/__init__.py
+++ b/can/io/__init__.py
@@ -8,7 +8,7 @@
from .player import LogReader, MessageSync
# Format specific
-from .asc import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader
+from .asc import ASCWriter, ASCReader
from .blf import BLFReader, BLFWriter
from .canutils import CanutilsLogReader, CanutilsLogWriter
from .csv import CSVWriter, CSVReader
diff --git a/can/io/asc.py b/can/io/asc.py
index db8f66358..16eaaa178 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -5,7 +5,6 @@
- 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
- under `test/data/logfile.asc`
"""
-import gzip
import re
from typing import Any, Generator, List, Optional, Dict, Union, TextIO
@@ -405,69 +404,3 @@ def on_message_received(self, msg: Message) -> None:
data=" ".join(data),
)
self.log_event(serialized, msg.timestamp)
-
-
-class GzipASCReader(ASCReader):
- """Gzipped version of :class:`~can.ASCReader`"""
-
- def __init__(
- self,
- file: Union[typechecking.FileLike, typechecking.StringPathLike],
- base: str = "hex",
- relative_timestamp: bool = True,
- ):
- """
- :param file: a path-like object or as file-like object to read from
- If this is a file-like object, is has to opened in text
- read mode, not binary read mode.
- :param base: Select the base(hex or dec) of id and data.
- If the header of the asc file contains base information,
- this value will be overwritten. Default "hex".
- :param relative_timestamp: Select whether the timestamps are
- `relative` (starting at 0.0) or `absolute` (starting at
- the system time). Default `True = relative`.
- """
- self._fileobj = None
- if file is not None and (hasattr(file, "read") and hasattr(file, "write")):
- # file is None or some file-like object
- self._fileobj = file
- super(GzipASCReader, self).__init__(
- gzip.open(file, mode="rt"), base, relative_timestamp
- )
-
- def stop(self) -> None:
- super(GzipASCReader, self).stop()
- if self._fileobj is not None:
- self._fileobj.close()
-
-
-class GzipASCWriter(ASCWriter):
- """Gzipped version of :class:`~can.ASCWriter`"""
-
- def __init__(
- self,
- file: Union[typechecking.FileLike, typechecking.StringPathLike],
- channel: int = 1,
- compresslevel: int = 6,
- ):
- """
- :param file: a path-like object or as file-like object to write to
- If this is a file-like object, is has to opened in text
- write mode, not binary write mode.
- :param channel: a default channel to use when the message does not
- have a channel set
- :param compresslevel: Gzip compresslevel, see
- :class:`~gzip.GzipFile` for details. The default is 6.
- """
- self._fileobj = None
- if file is not None and (hasattr(file, "read") and hasattr(file, "write")):
- # file is None or some file-like object
- self._fileobj = file
- super(GzipASCWriter, self).__init__(
- gzip.open(file, mode="wt", compresslevel=compresslevel), channel
- )
-
- def stop(self) -> None:
- super(GzipASCWriter, self).stop()
- if self._fileobj is not None:
- self._fileobj.close()
From 9b563bf9c69166ccd8a6a10639d2b1e5ad2e9ee4 Mon Sep 17 00:00:00 2001
From: TJ
Date: Tue, 25 Jan 2022 21:13:28 -0800
Subject: [PATCH 073/475] Remove unittests
---
test/logformats_test.py | 29 -----------------------------
1 file changed, 29 deletions(-)
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 3d4368a47..ffb7a75a3 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -11,7 +11,6 @@
TODO: correctly set preserves_channel and adds_default_channel
"""
-import gzip
import logging
import unittest
import tempfile
@@ -559,34 +558,6 @@ def test_ignore_comments(self):
_msg_list = self._read_log_file("logfile.asc")
-class TestGzipASCFileFormat(ReaderWriterTest):
- """Tests can.GzipASCWriter and can.GzipASCReader"""
-
- def _setup_instance(self):
- super()._setup_instance_helper(
- can.GzipASCWriter,
- can.GzipASCReader,
- binary_file=True,
- check_comments=True,
- preserves_channel=False,
- adds_default_channel=0,
- )
-
- def assertIncludesComments(self, filename):
- """
- Ensures that all comments are literally contained in the given file.
-
- :param filename: the path-like object to use
- """
- if self.original_comments:
- # read the entire outout file
- with gzip.open(filename, "rt" if self.binary_file else "r") as file:
- output_contents = file.read()
- # check each, if they can be found in there literally
- for comment in self.original_comments:
- self.assertIn(comment, output_contents)
-
-
class TestBlfFileFormat(ReaderWriterTest):
"""Tests can.BLFWriter and can.BLFReader.
From caeab0e80a355c2eba7de9950b5371c8eab3be5d Mon Sep 17 00:00:00 2001
From: TJ
Date: Tue, 25 Jan 2022 21:54:17 -0800
Subject: [PATCH 074/475] fix mypy errors
---
can/io/logger.py | 13 +++----------
can/io/player.py | 2 +-
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index 8f2611794..aff19f068 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -7,15 +7,8 @@
from abc import ABC, abstractmethod
from datetime import datetime
import gzip
-from typing import (
- Any,
- Optional,
- Callable,
- cast,
- IO,
- Type,
- Tuple,
-)
+from typing import Any, Optional, Callable, TextIO, Type, Tuple, Union, cast
+
from types import TracebackType
from typing_extensions import Literal
@@ -103,7 +96,7 @@ def __new__( # type: ignore
) from None
@staticmethod
- def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]:
+ def compress(filename: StringPathLike) -> Tuple[str, Union[str, Any]]:
"""
Return the suffix and io object of the decompressed file.
File will automatically recompress upon close.
diff --git a/can/io/player.py b/can/io/player.py
index 96d3a5b7c..e9a700bc0 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -95,7 +95,7 @@ def __new__( # type: ignore
@staticmethod
def decompress(
filename: "can.typechecking.StringPathLike",
- ) -> typing.Tuple[str, typing.IO[typing.Any]]:
+ ) -> typing.Tuple[str, typing.Union[str, typing.Any]]:
"""
Return the suffix and io object of the decompressed file.
"""
From c546bd03d6e8e6de1fdc2d2beed8df49965519c9 Mon Sep 17 00:00:00 2001
From: TJ
Date: Tue, 25 Jan 2022 22:28:37 -0800
Subject: [PATCH 075/475] edit unittest to work on windows
---
test/test_logger.py | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/test/test_logger.py b/test/test_logger.py
index bb3de8524..07cf17d37 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -8,8 +8,8 @@
from unittest import mock
from unittest.mock import Mock
import gzip
+import os
import sys
-import tempfile
import can
import can.logger
@@ -118,24 +118,25 @@ def setUp(self) -> None:
self.testmsg = can.Message(
arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True
)
-
self.baseargs = [sys.argv[0], "-i", "virtual"]
+ self.testfile = open("coffee.log.gz", "w+")
+
def test_compressed_logfile(self):
"""
Basic test to verify Logger is able to write gzip files.
"""
self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt])
-
- with tempfile.NamedTemporaryFile(suffix=".log.gz", delete=True) as compressed:
- sys.argv = self.baseargs + ["--file_name", compressed.name]
- can.logger.main()
- with gzip.open(compressed.name, "rt") as decompressed:
- last_line = decompressed.readlines()[-1]
+ sys.argv = self.baseargs + ["--file_name", self.testfile.name]
+ can.logger.main()
+ with gzip.open(self.testfile.name, "rt") as testlog:
+ last_line = testlog.readlines()[-1]
self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n")
def tearDown(self) -> None:
+ self.testfile.close()
+ os.remove(self.testfile.name)
self.patcher_virtual_bus.stop()
From 50071cc3b94faeede9a2136000bff059391ee4a0 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 26 Jan 2022 09:12:07 +0100
Subject: [PATCH 076/475] add missing type annotation
---
can/interfaces/vector/canlib.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index c3bc5413b..4a90ea1bc 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -11,8 +11,20 @@
import time
import os
from types import ModuleType
-from typing import List, NamedTuple, Optional, Tuple, Sequence, Union, Any, Dict
+from typing import (
+ List,
+ NamedTuple,
+ Optional,
+ Tuple,
+ Sequence,
+ Union,
+ Any,
+ Dict,
+ Callable,
+)
+WaitForSingleObject: Optional[Callable[[int, int], int]]
+INFINITE: Optional[int]
try:
# Try builtin Python 3 Windows API
from _winapi import WaitForSingleObject, INFINITE
From 72408e5b5a0875e0313c6457ff49e669cad4bfa6 Mon Sep 17 00:00:00 2001
From: Teejay
Date: Wed, 26 Jan 2022 17:35:01 -0800
Subject: [PATCH 077/475] Update can/io/logger.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/logger.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index aff19f068..8d8026e72 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -38,7 +38,9 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
* .log :class:`can.CanutilsLogWriter`
* .txt :class:`can.Printer`
- Or any of the above compressed using gzip (.gz)
+ Any of these formats can be used with gzip compression by appending
+ the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not
+ be able to read these files.
The **filename** may also be *None*, to fall back to :class:`can.Printer`.
From 3bdd60a6cf71e7e2d704d12c9289eda884cf4013 Mon Sep 17 00:00:00 2001
From: Teejay
Date: Wed, 26 Jan 2022 17:35:12 -0800
Subject: [PATCH 078/475] Update can/io/logger.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/logger.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index 8d8026e72..e9e1f7e77 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -30,7 +30,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
"""
Logs CAN messages to a file.
- The format is determined from the file format which can be one of:
+ The format is determined from the file suffix which can be one of:
* .asc: :class:`can.ASCWriter`
* .blf :class:`can.BLFWriter`
* .csv: :class:`can.CSVWriter`
From 9e1fe3ae6d518e44a835fabea54eea344bdb79c0 Mon Sep 17 00:00:00 2001
From: Teejay
Date: Wed, 26 Jan 2022 17:35:27 -0800
Subject: [PATCH 079/475] Update can/io/player.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/player.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/io/player.py b/can/io/player.py
index e9a700bc0..d68b4d236 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -25,7 +25,7 @@ class LogReader(BaseIOHandler):
"""
Replay logged CAN messages from a file.
- The format is determined from the file format which can be one of:
+ The format is determined from the file suffix which can be one of:
* .asc
* .blf
* .csv
From fe740504817c534fd459e252b54af8113e99837c Mon Sep 17 00:00:00 2001
From: Tbruno25
Date: Thu, 27 Jan 2022 01:37:52 +0000
Subject: [PATCH 080/475] Format code with black
---
can/io/logger.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index e9e1f7e77..ec73a62ec 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -38,8 +38,8 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
* .log :class:`can.CanutilsLogWriter`
* .txt :class:`can.Printer`
- Any of these formats can be used with gzip compression by appending
- the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not
+ Any of these formats can be used with gzip compression by appending
+ the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not
be able to read these files.
The **filename** may also be *None*, to fall back to :class:`can.Printer`.
From bfc3283e65225524d54fa81527e7775716b2a3ac Mon Sep 17 00:00:00 2001
From: TJ
Date: Wed, 26 Jan 2022 21:17:30 -0800
Subject: [PATCH 081/475] Update can/io/player.py
---
can/io/player.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/can/io/player.py b/can/io/player.py
index d68b4d236..dc60cf115 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -32,7 +32,9 @@ class LogReader(BaseIOHandler):
* .db
* .log
- Or any of the above compressed using gzip (.gz)
+ Gzip compressed files can be used as long as the original
+ files suffix is one of the above (e.g. filename.asc.gz).
+
Exposes a simple iterator interface, to use simply:
From 9f7842a909f3238a992a6e00b02271637cb191da Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Thu, 27 Jan 2022 07:38:16 +0100
Subject: [PATCH 082/475] Remove unnssesary conversion to list
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 65a9cb859..a60f3ac2f 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -51,4 +51,4 @@
}
)
-VALID_INTERFACES = frozenset(list(BACKENDS.keys()))
+VALID_INTERFACES = frozenset(BACKENDS.keys())
From 55c5fdfdac7e19b6662301e3ed52058b9e466c56 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 27 Jan 2022 10:35:29 +0100
Subject: [PATCH 083/475] improve io typing
---
can/__init__.py | 14 +++++------
can/io/asc.py | 14 +++++------
can/io/blf.py | 17 ++++++-------
can/io/canutils.py | 60 ++++++++++++++++++++++++++-------------------
can/io/csv.py | 21 +++++++++-------
can/io/generic.py | 11 ++++-----
can/io/logger.py | 21 ++++++++--------
can/io/player.py | 33 ++++++++++++-------------
can/io/printer.py | 11 ++++-----
can/io/sqlite.py | 20 ++++++++-------
can/listener.py | 28 ++++++++++-----------
can/typechecking.py | 4 +--
12 files changed, 131 insertions(+), 123 deletions(-)
diff --git a/can/__init__.py b/can/__init__.py
index 618ef347f..f21ffcc09 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -24,13 +24,6 @@
CanTimeoutError,
)
-from .io import Logger, SizedRotatingLogger, 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 .util import set_logging_level
from .message import Message
@@ -42,6 +35,13 @@
from .interface import Bus, detect_available_configs
from .bit_timing import BitTiming
+from .io import Logger, SizedRotatingLogger, 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 .broadcastmanager import (
CyclicSendTaskABC,
LimitedDurationCyclicSendTaskABC,
diff --git a/can/io/asc.py b/can/io/asc.py
index 16eaaa178..f8607d061 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -12,12 +12,10 @@
import time
import logging
-from .. import typechecking
from ..message import Message
-from ..listener import Listener
from ..util import channel2int
-from .generic import BaseIOHandler, FileIOMessageWriter
-from ..typechecking import AcceptedIOType
+from .generic import FileIOMessageWriter, MessageReader
+from ..typechecking import StringPathLike
CAN_MSG_EXT = 0x80000000
@@ -28,7 +26,7 @@
logger = logging.getLogger("can.io.asc")
-class ASCReader(BaseIOHandler):
+class ASCReader(MessageReader):
"""
Iterator of CAN messages from a ASC logging file. Meta data (comments,
bus statistics, J1939 Transport Protocol messages) is ignored.
@@ -40,7 +38,7 @@ class ASCReader(BaseIOHandler):
def __init__(
self,
- file: AcceptedIOType,
+ file: Union[StringPathLike, TextIO],
base: str = "hex",
relative_timestamp: bool = True,
) -> None:
@@ -248,7 +246,7 @@ def __iter__(self) -> Generator[Message, None, None]:
self.stop()
-class ASCWriter(FileIOMessageWriter, Listener):
+class ASCWriter(FileIOMessageWriter):
"""Logs CAN data to an ASCII log file (.asc).
The measurement starts with the timestamp of the first registered message.
@@ -287,7 +285,7 @@ class ASCWriter(FileIOMessageWriter, Listener):
def __init__(
self,
- file: AcceptedIOType,
+ file: Union[StringPathLike, TextIO],
channel: int = 1,
) -> None:
"""
diff --git a/can/io/blf.py b/can/io/blf.py
index 346d66cf6..e7be8979f 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -17,13 +17,12 @@
import datetime
import time
import logging
-from typing import List, BinaryIO
+from typing import List, BinaryIO, Generator, Union
from ..message import Message
-from ..listener import Listener
from ..util import len2dlc, dlc2len, channel2int
-from ..typechecking import AcceptedIOType
-from .generic import BaseIOHandler, FileIOMessageWriter
+from ..typechecking import StringPathLike
+from .generic import FileIOMessageWriter, MessageReader
class BLFParseError(Exception):
@@ -131,7 +130,7 @@ def systemtime_to_timestamp(systemtime):
return 0
-class BLFReader(BaseIOHandler):
+class BLFReader(MessageReader):
"""
Iterator of CAN messages from a Binary Logging File.
@@ -141,7 +140,7 @@ class BLFReader(BaseIOHandler):
file: BinaryIO
- def __init__(self, file: AcceptedIOType) -> None:
+ def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in binary
@@ -162,7 +161,7 @@ def __init__(self, file: AcceptedIOType) -> None:
self._tail = b""
self._pos = 0
- def __iter__(self):
+ def __iter__(self) -> Generator[Message, None, None]:
while True:
data = self.file.read(OBJ_HEADER_BASE_STRUCT.size)
if not data:
@@ -349,7 +348,7 @@ def _parse_data(self, data):
pos = next_pos
-class BLFWriter(FileIOMessageWriter, Listener):
+class BLFWriter(FileIOMessageWriter):
"""
Logs CAN data to a Binary Logging File compatible with Vector's tools.
"""
@@ -364,7 +363,7 @@ class BLFWriter(FileIOMessageWriter, Listener):
def __init__(
self,
- file: AcceptedIOType,
+ file: Union[StringPathLike, BinaryIO],
append: bool = False,
channel: int = 1,
compression_level: int = -1,
diff --git a/can/io/canutils.py b/can/io/canutils.py
index d3e122ae5..69793212c 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -5,11 +5,11 @@
"""
import logging
+from typing import Generator, TextIO, Union
from can.message import Message
-from can.listener import Listener
-from .generic import BaseIOHandler, FileIOMessageWriter
-from ..typechecking import AcceptedIOType
+from .generic import FileIOMessageWriter, MessageReader
+from ..typechecking import AcceptedIOType, StringPathLike
log = logging.getLogger("can.io.canutils")
@@ -22,7 +22,7 @@
CANFD_ESI = 0x02
-class CanutilsLogReader(BaseIOHandler):
+class CanutilsLogReader(MessageReader):
"""
Iterator over CAN messages from a .log Logging File (candump -L).
@@ -32,7 +32,9 @@ class CanutilsLogReader(BaseIOHandler):
``(0.0) vcan0 001#8d00100100820100``
"""
- def __init__(self, file: AcceptedIOType) -> None:
+ file: TextIO
+
+ def __init__(self, file: Union[StringPathLike, TextIO]) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in text
@@ -40,7 +42,7 @@ def __init__(self, file: AcceptedIOType) -> None:
"""
super().__init__(file, mode="r")
- def __iter__(self):
+ def __iter__(self) -> Generator[Message, None, None]:
for line in self.file:
# skip empty lines
@@ -48,14 +50,19 @@ def __iter__(self):
if not temp:
continue
- timestamp, channel, frame = temp.split()
- timestamp = float(timestamp[1:-1])
- canId, data = frame.split("#", maxsplit=1)
- if channel.isdigit():
- channel = int(channel)
+ channel_string: str
+ timestamp_string, channel_string, frame = temp.split()
+ timestamp = float(timestamp_string[1:-1])
+ can_id_string, data = frame.split("#", maxsplit=1)
+
+ channel: Union[int, str]
+ if channel_string.isdigit():
+ channel = int(channel_string)
+ else:
+ channel = channel_string
- isExtended = len(canId) > 3
- canId = int(canId, 16)
+ is_extended = len(can_id_string) > 3
+ can_id = int(can_id_string, 16)
is_fd = False
brs = False
@@ -69,35 +76,35 @@ def __iter__(self):
data = data[2:]
if data and data[0].lower() == "r":
- isRemoteFrame = True
+ is_remote_frame = True
if len(data) > 1:
dlc = int(data[1:])
else:
dlc = 0
- dataBin = None
+ data_bin = None
else:
- isRemoteFrame = False
+ is_remote_frame = False
dlc = len(data) // 2
- dataBin = bytearray()
+ data_bin = bytearray()
for i in range(0, len(data), 2):
- dataBin.append(int(data[i : (i + 2)], 16))
+ data_bin.append(int(data[i : (i + 2)], 16))
- if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR:
+ if can_id & CAN_ERR_FLAG and can_id & CAN_ERR_BUSERROR:
msg = Message(timestamp=timestamp, is_error_frame=True)
else:
msg = Message(
timestamp=timestamp,
- arbitration_id=canId & 0x1FFFFFFF,
- is_extended_id=isExtended,
- is_remote_frame=isRemoteFrame,
+ arbitration_id=can_id & 0x1FFFFFFF,
+ is_extended_id=is_extended,
+ is_remote_frame=is_remote_frame,
is_fd=is_fd,
bitrate_switch=brs,
error_state_indicator=esi,
dlc=dlc,
- data=dataBin,
+ data=data_bin,
channel=channel,
)
yield msg
@@ -105,7 +112,7 @@ def __iter__(self):
self.stop()
-class CanutilsLogWriter(FileIOMessageWriter, Listener):
+class CanutilsLogWriter(FileIOMessageWriter):
"""Logs CAN data to an ASCII log file (.log).
This class is is compatible with "candump -L".
@@ -115,7 +122,10 @@ class CanutilsLogWriter(FileIOMessageWriter, Listener):
"""
def __init__(
- self, file: AcceptedIOType, channel: str = "vcan0", append: bool = False
+ self,
+ file: Union[StringPathLike, TextIO],
+ channel: str = "vcan0",
+ append: bool = False,
):
"""
:param file: a path-like object or as file-like object to write to
diff --git a/can/io/csv.py b/can/io/csv.py
index fa3175a2d..0161b4f55 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -10,15 +10,14 @@
"""
from base64 import b64encode, b64decode
-from typing import TextIO
+from typing import TextIO, Generator, Union
from can.message import Message
-from can.listener import Listener
-from .generic import BaseIOHandler, FileIOMessageWriter
-from ..typechecking import AcceptedIOType
+from .generic import FileIOMessageWriter, MessageReader
+from ..typechecking import StringPathLike
-class CSVReader(BaseIOHandler):
+class CSVReader(MessageReader):
"""Iterator over CAN messages from a .csv file that was
generated by :class:`~can.CSVWriter` or that uses the same
format as described there. Assumes that there is a header
@@ -27,7 +26,9 @@ class CSVReader(BaseIOHandler):
Any line separator is accepted.
"""
- def __init__(self, file: AcceptedIOType) -> None:
+ file: TextIO
+
+ def __init__(self, file: Union[StringPathLike, TextIO]) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in text
@@ -35,7 +36,7 @@ def __init__(self, file: AcceptedIOType) -> None:
"""
super().__init__(file, mode="r")
- def __iter__(self):
+ def __iter__(self) -> Generator[Message, None, None]:
# skip the header line
try:
next(self.file)
@@ -62,7 +63,7 @@ def __iter__(self):
self.stop()
-class CSVWriter(FileIOMessageWriter, Listener):
+class CSVWriter(FileIOMessageWriter):
"""Writes a comma separated text file with a line for
each message. Includes a header line.
@@ -85,7 +86,9 @@ class CSVWriter(FileIOMessageWriter, Listener):
file: TextIO
- def __init__(self, file: AcceptedIOType, append: bool = False) -> None:
+ def __init__(
+ self, file: Union[StringPathLike, TextIO], append: bool = False
+ ) -> None:
"""
:param file: a path-like object or a file-like object to write to.
If this is a file-like object, is has to open in text
diff --git a/can/io/generic.py b/can/io/generic.py
index 96ff91abf..6f18fbe65 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -5,9 +5,6 @@
Optional,
cast,
Iterable,
- Union,
- TextIO,
- BinaryIO,
Type,
ContextManager,
)
@@ -76,15 +73,17 @@ def stop(self) -> None:
class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta):
"""The base class for all writers."""
+ file: Optional[can.typechecking.FileLike]
+
# pylint: disable=abstract-method,too-few-public-methods
class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
"""A specialized base class for all writers with file descriptors."""
- file: Union[TextIO, BinaryIO]
+ file: can.typechecking.FileLike
def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None:
- # Not possible with the type signature, but be verbose for user friendliness
+ # Not possible with the type signature, but be verbose for user-friendliness
if file is None:
raise ValueError("The given file cannot be None")
@@ -92,5 +91,5 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> N
# pylint: disable=too-few-public-methods
-class MessageReader(BaseIOHandler, Iterable, metaclass=ABCMeta):
+class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta):
"""The base class for all readers."""
diff --git a/can/io/logger.py b/can/io/logger.py
index ec73a62ec..cbed054ac 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -7,7 +7,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
import gzip
-from typing import Any, Optional, Callable, TextIO, Type, Tuple, Union, cast
+from typing import Any, Optional, Callable, Type, Tuple, cast, Dict
from types import TracebackType
@@ -16,17 +16,17 @@
from ..message import Message
from ..listener import Listener
-from .generic import BaseIOHandler, FileIOMessageWriter
+from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
from .asc import ASCWriter
from .blf import BLFWriter
from .canutils import CanutilsLogWriter
from .csv import CSVWriter
from .sqlite import SqliteWriter
from .printer import Printer
-from ..typechecking import StringPathLike
+from ..typechecking import StringPathLike, FileLike, AcceptedIOType
-class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
+class Logger(MessageWriter): # pylint: disable=abstract-method
"""
Logs CAN messages to a file.
@@ -52,7 +52,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
"""
fetched_plugins = False
- message_writers = {
+ message_writers: Dict[str, Type[MessageWriter]] = {
".asc": ASCWriter,
".blf": BLFWriter,
".csv": CSVWriter,
@@ -64,7 +64,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
@staticmethod
def __new__( # type: ignore
cls: Any, filename: Optional[StringPathLike], *args: Any, **kwargs: Any
- ) -> Listener:
+ ) -> MessageWriter:
"""
:param filename: the filename/path of the file to write to,
may be a path-like object or None to
@@ -85,20 +85,19 @@ def __new__( # type: ignore
suffix = pathlib.PurePath(filename).suffix.lower()
+ file_or_filename: AcceptedIOType = filename
if suffix == ".gz":
- suffix, filename = Logger.compress(filename)
+ suffix, file_or_filename = Logger.compress(filename)
try:
- return cast(
- Listener, Logger.message_writers[suffix](filename, *args, **kwargs)
- )
+ return Logger.message_writers[suffix](file_or_filename, *args, **kwargs)
except KeyError:
raise ValueError(
f'No write support for this unknown log format "{suffix}"'
) from None
@staticmethod
- def compress(filename: StringPathLike) -> Tuple[str, Union[str, Any]]:
+ def compress(filename: StringPathLike) -> Tuple[str, FileLike]:
"""
Return the suffix and io object of the decompressed file.
File will automatically recompress upon close.
diff --git a/can/io/player.py b/can/io/player.py
index dc60cf115..132751f4d 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -10,18 +10,17 @@
from pkg_resources import iter_entry_points
-if typing.TYPE_CHECKING:
- import can
-
-from .generic import BaseIOHandler, MessageReader
+from .generic import MessageReader
from .asc import ASCReader
from .blf import BLFReader
from .canutils import CanutilsLogReader
from .csv import CSVReader
from .sqlite import SqliteReader
+from ..typechecking import StringPathLike, FileLike, AcceptedIOType
+from ..message import Message
-class LogReader(BaseIOHandler):
+class LogReader(MessageReader):
"""
Replay logged CAN messages from a file.
@@ -51,7 +50,7 @@ class LogReader(BaseIOHandler):
"""
fetched_plugins = False
- message_readers = {
+ message_readers: typing.Dict[str, typing.Type[MessageReader]] = {
".asc": ASCReader,
".blf": BLFReader,
".csv": CSVReader,
@@ -62,7 +61,7 @@ class LogReader(BaseIOHandler):
@staticmethod
def __new__( # type: ignore
cls: typing.Any,
- filename: "can.typechecking.StringPathLike",
+ filename: StringPathLike,
*args: typing.Any,
**kwargs: typing.Any,
) -> MessageReader:
@@ -81,14 +80,11 @@ def __new__( # type: ignore
suffix = pathlib.PurePath(filename).suffix.lower()
+ file_or_filename: AcceptedIOType = filename
if suffix == ".gz":
- suffix, filename = LogReader.decompress(filename)
-
+ suffix, file_or_filename = LogReader.decompress(filename)
try:
- return typing.cast(
- MessageReader,
- LogReader.message_readers[suffix](filename, *args, **kwargs),
- )
+ return LogReader.message_readers[suffix](file_or_filename, *args, **kwargs)
except KeyError:
raise ValueError(
f'No read support for this unknown log format "{suffix}"'
@@ -96,8 +92,8 @@ def __new__( # type: ignore
@staticmethod
def decompress(
- filename: "can.typechecking.StringPathLike",
- ) -> typing.Tuple[str, typing.Union[str, typing.Any]]:
+ filename: StringPathLike,
+ ) -> typing.Tuple[str, typing.Union[str, FileLike]]:
"""
Return the suffix and io object of the decompressed file.
"""
@@ -106,6 +102,9 @@ def decompress(
return real_suffix, gzip.open(filename, mode)
+ def __iter__(self) -> typing.Generator[Message, None, None]:
+ pass
+
class MessageSync: # pylint: disable=too-few-public-methods
"""
@@ -114,7 +113,7 @@ class MessageSync: # pylint: disable=too-few-public-methods
def __init__(
self,
- messages: typing.Iterable["can.Message"],
+ messages: typing.Iterable[Message],
timestamps: bool = True,
gap: float = 0.0001,
skip: float = 60.0,
@@ -132,7 +131,7 @@ def __init__(
self.gap = gap
self.skip = skip
- def __iter__(self) -> typing.Generator["can.Message", None, None]:
+ def __iter__(self) -> typing.Generator[Message, None, None]:
playback_start_time = time()
recorded_start_time = None
diff --git a/can/io/printer.py b/can/io/printer.py
index 09c86f81f..cafab3815 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -4,17 +4,16 @@
import logging
-from typing import Optional, cast, TextIO
+from typing import Optional, TextIO, Union
from ..message import Message
-from ..listener import Listener
-from .generic import BaseIOHandler
-from ..typechecking import AcceptedIOType
+from .generic import MessageWriter
+from ..typechecking import StringPathLike
log = logging.getLogger("can.io.printer")
-class Printer(BaseIOHandler, Listener):
+class Printer(MessageWriter):
"""
The Printer class is a subclass of :class:`~can.Listener` which simply prints
any messages it receives to the terminal (stdout). A message is turned into a
@@ -27,7 +26,7 @@ class Printer(BaseIOHandler, Listener):
file: Optional[TextIO]
def __init__(
- self, file: Optional[AcceptedIOType] = None, append: bool = False
+ self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False
) -> None:
"""
:param file: An optional path-like object or a file-like object to "print"
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index 8d184bce1..5f05764d5 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -8,15 +8,17 @@
import threading
import logging
import sqlite3
+from typing import Generator
from can.listener import BufferedReader
from can.message import Message
-from .generic import BaseIOHandler
+from .generic import MessageWriter, MessageReader
+from ..typechecking import StringPathLike
log = logging.getLogger("can.io.sqlite")
-class SqliteReader(BaseIOHandler):
+class SqliteReader(MessageReader):
"""
Reads recorded CAN messages from a simple SQL database.
@@ -30,9 +32,9 @@ class SqliteReader(BaseIOHandler):
.. note:: The database schema is given in the documentation of the loggers.
"""
- def __init__(self, file, table_name="messages"):
+ def __init__(self, file: StringPathLike, table_name: str = "messages") -> None:
"""
- :param file: a `str` or since Python 3.7 a path like object that points
+ :param file: a `str` path like object that points
to the database file to use
:param str table_name: the name of the table to look for the messages
@@ -45,7 +47,7 @@ def __init__(self, file, table_name="messages"):
self._cursor = self._conn.cursor()
self.table_name = table_name
- def __iter__(self):
+ def __iter__(self) -> Generator[Message, None, None]:
for frame_data in self._cursor.execute(f"SELECT * FROM {self.table_name}"):
yield SqliteReader._assemble_message(frame_data)
@@ -81,7 +83,7 @@ def stop(self):
self._conn.close()
-class SqliteWriter(BaseIOHandler, BufferedReader):
+class SqliteWriter(MessageWriter, BufferedReader):
"""Logs received CAN data to a simple SQL database.
The sqlite database may already exist, otherwise it will
@@ -126,9 +128,9 @@ class SqliteWriter(BaseIOHandler, BufferedReader):
MAX_BUFFER_SIZE_BEFORE_WRITES = 500
"""Maximum number of messages to buffer before writing to the database"""
- def __init__(self, file, table_name="messages"):
+ def __init__(self, file: StringPathLike, table_name: str = "messages") -> None:
"""
- :param file: a `str` or since Python 3.7 a path like object that points
+ :param file: a `str` or path like object that points
to the database file to use
:param str table_name: the name of the table to store messages in
@@ -229,4 +231,4 @@ def stop(self):
BufferedReader.stop(self)
self._stop_running_event.set()
self._writer_thread.join()
- BaseIOHandler.stop(self)
+ MessageReader.stop(self)
diff --git a/can/listener.py b/can/listener.py
index 8ed4f7b77..8b90fc79e 100644
--- a/can/listener.py
+++ b/can/listener.py
@@ -4,22 +4,14 @@
import sys
import warnings
+import asyncio
+from abc import ABCMeta, abstractmethod
+from queue import SimpleQueue, Empty
from typing import Any, AsyncIterator, Awaitable, Optional
from can.message import Message
from can.bus import BusABC
-from abc import ABCMeta, abstractmethod
-
-try:
- # Python 3.7
- from queue import SimpleQueue, Empty
-except ImportError:
- # Python 3.0 - 3.6
- from queue import Queue as SimpleQueue, Empty # type: ignore
-
-import asyncio
-
class Listener(metaclass=ABCMeta):
"""The basic listener that can be called directly to handle some
@@ -37,6 +29,9 @@ class Listener(metaclass=ABCMeta):
listener.stop()
"""
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ pass
+
@abstractmethod
def on_message_received(self, msg: Message) -> None:
"""This method is called to handle the given message.
@@ -68,7 +63,8 @@ class RedirectReader(Listener):
A RedirectReader sends all received messages to another Bus.
"""
- def __init__(self, bus: BusABC) -> None:
+ def __init__(self, bus: BusABC, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
self.bus = bus
def on_message_received(self, msg: Message) -> None:
@@ -89,7 +85,9 @@ class BufferedReader(Listener):
:attr is_stopped: ``True`` if the reader has been stopped
"""
- def __init__(self) -> None:
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+
# set to "infinite" size
self.buffer: SimpleQueue[Message] = SimpleQueue()
self.is_stopped: bool = False
@@ -139,7 +137,9 @@ class AsyncBufferedReader(Listener):
print(msg)
"""
- def __init__(self, **kwargs: Any) -> None:
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+
self.buffer: "asyncio.Queue[Message]"
if "loop" in kwargs:
diff --git a/can/typechecking.py b/can/typechecking.py
index 3e3cca833..ed76b6c85 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -1,6 +1,6 @@
"""Types for mypy type-checking
"""
-
+import gzip
import typing
if typing.TYPE_CHECKING:
@@ -27,7 +27,7 @@
Channel = typing.Union[ChannelInt, ChannelStr]
# Used by the IO module
-FileLike = typing.Union[typing.TextIO, typing.BinaryIO]
+FileLike = typing.Union[typing.TextIO, typing.BinaryIO, gzip.GzipFile]
StringPathLike = typing.Union[str, "os.PathLike[str]"]
AcceptedIOType = typing.Union[FileLike, StringPathLike]
From f1808ed5e8e20ebbf489713fe5da4a44fea079f0 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:34:07 +0100
Subject: [PATCH 084/475] Address pylint issues (#1239)
* Adress pylint issues
* Format code with black
* Fix line-too-long
* Fix abstract-method
* Fix line-too-long
Co-authored-by: felixdivo
---
can/broadcastmanager.py | 4 ++--
can/exceptions.py | 3 ++-
can/interface.py | 11 ++++-------
can/listener.py | 6 +++---
can/logconvert.py | 2 +-
can/viewer.py | 19 ++++++++++---------
6 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py
index 90cf0ce60..c15186e2c 100644
--- a/can/broadcastmanager.py
+++ b/can/broadcastmanager.py
@@ -19,7 +19,7 @@
import threading
import time
-# try to import win32event for event-based cyclic send task(needs pywin32 package)
+# try to import win32event for event-based cyclic send task (needs the pywin32 package)
try:
import win32event
@@ -260,7 +260,7 @@ def stop(self) -> None:
def start(self) -> None:
self.stopped = False
if self.thread is None or not self.thread.is_alive():
- name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id)
+ name = f"Cyclic send task for 0x{self.messages[0].arbitration_id:X}"
self.thread = threading.Thread(target=self._run, name=name)
self.thread.daemon = True
diff --git a/can/exceptions.py b/can/exceptions.py
index aec0dfd1d..5a7aa0b7c 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -14,6 +14,7 @@
For example, validating typical arguments and parameters might result in a
:class:`ValueError`. This should always be documented for the function at hand.
"""
+
import sys
from contextlib import contextmanager
@@ -114,7 +115,7 @@ def error_check(
"""Catches any exceptions and turns them into the new type while preserving the stack trace."""
try:
yield
- except Exception as error:
+ except Exception as error: # pylint: disable=broad-except
if error_message is None:
raise exception_type(str(error)) from error
else:
diff --git a/can/interface.py b/can/interface.py
index 5282d77bf..e217f2fb6 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -32,7 +32,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
module_name, class_name = BACKENDS[interface]
except KeyError:
raise NotImplementedError(
- "CAN interface '{}' not supported".format(interface)
+ f"CAN interface '{interface}' not supported"
) from None
# Import the correct interface module
@@ -40,9 +40,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
module = importlib.import_module(module_name)
except Exception as e:
raise CanInterfaceNotImplementedError(
- "Cannot import module {} for CAN interface '{}': {}".format(
- module_name, interface, e
- )
+ f"Cannot import module {module_name} for CAN interface '{interface}': {e}"
) from None
# Get the correct class
@@ -50,9 +48,8 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
bus_class = getattr(module, class_name)
except Exception as e:
raise CanInterfaceNotImplementedError(
- "Cannot import class {} from module {} for CAN interface '{}': {}".format(
- class_name, module_name, interface, e
- )
+ f"Cannot import class {class_name} from module {module_name} for CAN interface "
+ f"'{interface}': {e}"
) from None
return cast(Type[BusABC], bus_class)
diff --git a/can/listener.py b/can/listener.py
index 8b90fc79e..12836a83c 100644
--- a/can/listener.py
+++ b/can/listener.py
@@ -58,7 +58,7 @@ def stop(self) -> None:
"""
-class RedirectReader(Listener):
+class RedirectReader(Listener): # pylint: disable=abstract-method
"""
A RedirectReader sends all received messages to another Bus.
"""
@@ -71,7 +71,7 @@ def on_message_received(self, msg: Message) -> None:
self.bus.send(msg)
-class BufferedReader(Listener):
+class BufferedReader(Listener): # pylint: disable=abstract-method
"""
A BufferedReader is a subclass of :class:`~can.Listener` which implements a
**message buffer**: that is, when the :class:`can.BufferedReader` instance is
@@ -126,7 +126,7 @@ def stop(self) -> None:
self.is_stopped = True
-class AsyncBufferedReader(Listener):
+class AsyncBufferedReader(Listener): # pylint: disable=abstract-method
"""A message buffer for use with :mod:`asyncio`.
See :ref:`asyncio` for how to use with :class:`can.Notifier`.
diff --git a/can/logconvert.py b/can/logconvert.py
index 6a2f52341..730e82304 100644
--- a/can/logconvert.py
+++ b/can/logconvert.py
@@ -12,7 +12,7 @@
class ArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.print_help(sys.stderr)
- self.exit(errno.EINVAL, "%s: error: %s\n" % (self.prog, message))
+ self.exit(errno.EINVAL, f"{self.prog}: error: {message}\n")
def main():
diff --git a/can/viewer.py b/can/viewer.py
index b74e954a0..9cd5246fb 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -29,7 +29,6 @@
import time
from typing import Dict, List, Tuple, Union
-import can
from can import __version__
from .logger import (
_create_bus,
@@ -53,7 +52,7 @@
curses = None # type: ignore
-class CanViewer:
+class CanViewer: # pylint: disable=too-many-instance-attributes
def __init__(self, stdscr, bus, data_structs, testing=False):
self.stdscr = stdscr
self.bus = bus
@@ -89,7 +88,7 @@ def run(self):
# Clear the terminal and draw the header
self.draw_header()
- while 1:
+ while True:
# Do not read the CAN-Bus when in paused mode
if not self.paused:
# Read the CAN-Bus and draw it in the terminal window
@@ -237,8 +236,11 @@ def draw_can_bus_message(self, msg, sorting=False):
self.ids[key]["count"] += 1
# Format the CAN-Bus ID as a hex value
- arbitration_id_string = "0x{0:0{1}X}".format(
- msg.arbitration_id, 8 if msg.is_extended_id else 3
+ arbitration_id_string = (
+ "0x{0:0{1}X}".format( # pylint: disable=consider-using-f-string
+ msg.arbitration_id,
+ 8 if msg.is_extended_id else 3,
+ )
)
# Use red for error frames
@@ -263,7 +265,7 @@ def draw_can_bus_message(self, msg, sorting=False):
previous_byte_values = self.previous_values[key]
except KeyError: # no row of previous values exists for the current message ID
# initialise a row to store the values for comparison next time
- self.previous_values[key] = dict()
+ self.previous_values[key] = {}
previous_byte_values = self.previous_values[key]
for i, b in enumerate(msg.data):
col = 52 + i * 3
@@ -279,7 +281,7 @@ def draw_can_bus_message(self, msg, sorting=False):
else:
data_color = color
except KeyError:
- # previous entry for byte didnt exist - default to rest of line colour
+ # previous entry for byte didn't exist - default to rest of line colour
data_color = color
finally:
# write the new value to the previous values dict for next time
@@ -336,7 +338,7 @@ def draw_header(self):
def redraw_screen(self):
# Trigger a complete redraw
self.draw_header()
- for key, ids in self.ids.items():
+ for ids in self.ids.values():
self.draw_can_bus_message(ids["msg"])
@@ -545,7 +547,6 @@ def main() -> None:
if can_filters:
additional_config.update({"can_filters": can_filters})
bus = _create_bus(parsed_args, **additional_config)
- # print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}")
curses.wrapper(CanViewer, bus, data_structs)
From 48f9c271a80993bc5649de6e19e6dabf547949e9 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Thu, 27 Jan 2022 17:05:41 +0100
Subject: [PATCH 085/475] Fix deprecation of asyncio.get_event_loop() (#1235)
* Fix deprecation in test case
* Modernize example
* Better test_asyncio_notifier()
* Format code with black
* Attempt to fix typing; improve docs
* try to fix mypy failure with type annotation
* Better bus handling
* Doc fix
* Fix type errors in notifier.py
* Format code with black
* Fix example typing
* Fix too long line in doc
* Switch name from Listenable to MessageRecipient
Co-authored-by: felixdivo
---
can/interface.py | 2 +-
can/notifier.py | 38 +++++++++++--------
examples/asyncio_demo.py | 80 +++++++++++++++++++---------------------
examples/serial_com.py | 6 +--
test/notifier_test.py | 70 +++++++++++++++++------------------
5 files changed, 98 insertions(+), 98 deletions(-)
diff --git a/can/interface.py b/can/interface.py
index e217f2fb6..f1a0087e3 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -103,7 +103,7 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
# resolve the bus class to use for that interface
cls = _get_class_for_interface(kwargs["interface"])
- # remove the 'interface' key so it doesn't get passed to the backend
+ # remove the "interface" key, so it doesn't get passed to the backend
del kwargs["interface"]
# make sure the bus can handle this config format
diff --git a/can/notifier.py b/can/notifier.py
index 2554fb4fc..f7c004c4e 100644
--- a/can/notifier.py
+++ b/can/notifier.py
@@ -2,29 +2,30 @@
This module contains the implementation of :class:`~can.Notifier`.
"""
-from typing import Any, cast, Iterable, List, Optional, Union, Awaitable
+import asyncio
+import logging
+import threading
+import time
+from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable
from can.bus import BusABC
from can.listener import Listener
from can.message import Message
-import threading
-import logging
-import time
-import asyncio
-
logger = logging.getLogger("can.Notifier")
+MessageRecipient = Union[Listener, Callable[[Message], None]]
+
class Notifier:
def __init__(
self,
bus: Union[BusABC, List[BusABC]],
- listeners: Iterable[Listener],
+ listeners: Iterable[MessageRecipient],
timeout: float = 1.0,
loop: Optional[asyncio.AbstractEventLoop] = None,
) -> None:
- """Manages the distribution of :class:`can.Message` instances to listeners.
+ """Manages the distribution of :class:`~can.Message` instances to listeners.
Supports multiple buses and listeners.
@@ -35,11 +36,13 @@ def __init__(
:param bus: A :ref:`bus` or a list of buses to listen to.
- :param listeners: An iterable of :class:`~can.Listener`
- :param timeout: An optional maximum number of seconds to wait for any message.
- :param loop: An :mod:`asyncio` event loop to schedule listeners in.
+ :param listeners:
+ An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message`
+ and return nothing.
+ :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`.
+ :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in.
"""
- self.listeners: List[Listener] = list(listeners)
+ self.listeners: List[MessageRecipient] = list(listeners)
self.bus = bus
self.timeout = timeout
self._loop = loop
@@ -101,8 +104,8 @@ def stop(self, timeout: float = 5) -> None:
# reader is a file descriptor
self._loop.remove_reader(reader)
for listener in self.listeners:
- if hasattr(listener, "stop"):
- listener.stop()
+ # Mypy prefers this over a hasattr(...) check
+ getattr(listener, "stop", lambda: None)()
def _rx_thread(self, bus: BusABC) -> None:
msg = None
@@ -150,9 +153,12 @@ def _on_error(self, exc: Exception) -> bool:
was_handled = False
for listener in self.listeners:
- if hasattr(listener, "on_error"):
+ on_error = getattr(
+ listener, "on_error", None
+ ) # Mypy prefers this over hasattr(...)
+ if on_error is not None:
try:
- listener.on_error(exc)
+ on_error(exc)
except NotImplementedError:
pass
else:
diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py
index d501d1aaf..0f37d6573 100755
--- a/examples/asyncio_demo.py
+++ b/examples/asyncio_demo.py
@@ -5,57 +5,53 @@
"""
import asyncio
+from typing import List
+
import can
+from can.notifier import MessageRecipient
-def print_message(msg):
+def print_message(msg: can.Message) -> None:
"""Regular callback function. Can also be a coroutine."""
print(msg)
-async def main():
+async def main() -> None:
"""The main function that runs in the loop."""
- bus = can.Bus("vcan0", bustype="virtual", receive_own_messages=True)
- reader = can.AsyncBufferedReader()
- logger = can.Logger("logfile.asc")
-
- listeners = [
- print_message, # Callback function
- reader, # AsyncBufferedReader() listener
- logger, # Regular Listener object
- ]
- # Create Notifier with an explicit loop to use for scheduling of callbacks
- loop = asyncio.get_event_loop()
- notifier = can.Notifier(bus, listeners, loop=loop)
- # Start sending first message
- bus.send(can.Message(arbitration_id=0))
-
- print("Bouncing 10 messages...")
- for _ in range(10):
- # Wait for next message from AsyncBufferedReader
- msg = await reader.get_message()
- # Delay response
- await asyncio.sleep(0.5)
- msg.arbitration_id += 1
- bus.send(msg)
- # Wait for last message to arrive
- await reader.get_message()
- print("Done!")
-
- # Clean-up
- notifier.stop()
- bus.shutdown()
+ with can.Bus( # type: ignore
+ interface="virtual", channel="my_channel_0", receive_own_messages=True
+ ) as bus:
+ reader = can.AsyncBufferedReader()
+ logger = can.Logger("logfile.asc")
+
+ listeners: List[MessageRecipient] = [
+ print_message, # Callback function
+ reader, # AsyncBufferedReader() listener
+ logger, # Regular Listener object
+ ]
+ # Create Notifier with an explicit loop to use for scheduling of callbacks
+ loop = asyncio.get_running_loop()
+ notifier = can.Notifier(bus, listeners, loop=loop)
+ # Start sending first message
+ bus.send(can.Message(arbitration_id=0))
+
+ print("Bouncing 10 messages...")
+ for _ in range(10):
+ # Wait for next message from AsyncBufferedReader
+ msg = await reader.get_message()
+ # Delay response
+ await asyncio.sleep(0.5)
+ msg.arbitration_id += 1
+ bus.send(msg)
+
+ # Wait for last message to arrive
+ await reader.get_message()
+ print("Done!")
+
+ # Clean-up
+ notifier.stop()
if __name__ == "__main__":
- try:
- # Get the default event loop
- LOOP = asyncio.get_event_loop()
- # Run until main coroutine finishes
- LOOP.run_until_complete(main())
- finally:
- LOOP.close()
-
- # or on Python 3.7+ simply
- # asyncio.run(main())
+ asyncio.run(main())
diff --git a/examples/serial_com.py b/examples/serial_com.py
index 1fbc997b2..c57207a77 100755
--- a/examples/serial_com.py
+++ b/examples/serial_com.py
@@ -47,9 +47,9 @@ def receive(bus, stop_event):
def main():
- """Controles the sender and receiver."""
- with can.interface.Bus(bustype="serial", channel="/dev/ttyS10") as server:
- with can.interface.Bus(bustype="serial", channel="/dev/ttyS11") as client:
+ """Controls the sender and receiver."""
+ with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server:
+ with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client:
tx_msg = can.Message(
arbitration_id=0x01,
diff --git a/test/notifier_test.py b/test/notifier_test.py
index c9d8f4a27..ca2093f55 100644
--- a/test/notifier_test.py
+++ b/test/notifier_test.py
@@ -9,48 +9,46 @@
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()
- bus.shutdown()
+ with can.Bus("test", interface="virtual", receive_own_messages=True) as bus:
+ 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()
- bus1.shutdown()
- bus2.shutdown()
+ with can.Bus(0, interface="virtual", receive_own_messages=True) as bus1:
+ with can.Bus(1, interface="virtual", receive_own_messages=True) as bus2:
+ 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()
class AsyncNotifierTest(unittest.TestCase):
def test_asyncio_notifier(self):
- loop = asyncio.get_event_loop()
- bus = can.Bus("test", bustype="virtual", receive_own_messages=True)
- reader = can.AsyncBufferedReader()
- notifier = can.Notifier(bus, [reader], 0.1, loop=loop)
- msg = can.Message()
- bus.send(msg)
- future = asyncio.wait_for(reader.get_message(), 1.0)
- recv_msg = loop.run_until_complete(future)
- self.assertIsNotNone(recv_msg)
- notifier.stop()
- bus.shutdown()
+ async def run_it():
+ with can.Bus("test", interface="virtual", receive_own_messages=True) as bus:
+ reader = can.AsyncBufferedReader()
+ notifier = can.Notifier(
+ bus, [reader], 0.1, loop=asyncio.get_running_loop()
+ )
+ bus.send(can.Message())
+ recv_msg = await asyncio.wait_for(reader.get_message(), 0.5)
+ self.assertIsNotNone(recv_msg)
+ notifier.stop()
+
+ asyncio.run(run_it())
if __name__ == "__main__":
From c7ba84a012d9e56fa3d29a8cf2b87d3706f12988 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Thu, 27 Jan 2022 17:06:21 +0100
Subject: [PATCH 086/475] Fix entry_points deprecation (#1233)
* Fix entry_points deprectaion
* Format code with black
* Undo change that shall be in a separate PR
Co-authored-by: felixdivo
---
can/interfaces/__init__.py | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index a60f3ac2f..90a05d7bc 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -32,14 +32,16 @@
try:
from importlib.metadata import entry_points
- entry = entry_points()
- if "can.interface" in entry:
- BACKENDS.update(
- {
- interface.name: tuple(interface.value.split(":"))
- for interface in entry["can.interface"]
- }
- )
+ try:
+ entries = entry_points(group="can.interface")
+ except TypeError:
+ # Fallback for Python <3.10
+ # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note"
+ entries = entry_points().get("can.interface", [])
+
+ BACKENDS.update(
+ {interface.name: tuple(interface.value.split(":")) for interface in entries}
+ )
except ImportError:
from pkg_resources import iter_entry_points
From 24ea2f2a4dbb6e72b41dc88646d0fc22ac1e10da Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Fri, 28 Jan 2022 09:12:56 +0100
Subject: [PATCH 087/475] Update changelog (#1240)
---
CHANGELOG.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1428075d..1f57a4b17 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -63,6 +63,7 @@ Improved interfaces
* Add more interface information to channel config (#917)
* Improve timestamp accuracy on Windows (#934, #936)
* Fix error with VN8900 (#1184)
+ * Add static typing (#1229)
* PCAN
* Do not incorrectly reset CANMsg.MSGTYPE on remote frame (#659, #681)
* Add support for error frames (#711)
@@ -118,9 +119,10 @@ Other API changes and improvements
* Logger, viewer and player tools can handle CAN FD (#632)
* Many bugfixes and more testing coverage
* IO
- * Log rotation (#648, #874, #881, #1147)
+ * [Log rotation](https://python-can.readthedocs.io/en/develop/listeners.html#can.SizedRotatingLogger) (#648, #874, #881, #1147)
+ * Transparent (de)compression of [gzip](https://docs.python.org/3/library/gzip.html) files for all formats (#1221)
* Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783)
- * ASCReader/Writer enhancements (#820)
+ * ASCReader/Writer enhancements like increased robustness (#820, #1223)
* Adding absolute timestamps to ASC reader (#761)
* Support other base number (radix) at ASCReader (#764)
* Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194)
@@ -135,7 +137,7 @@ Other API changes and improvements
* Changes to serial device number decoding (#869)
* Add a default fileno function to the BusABC (#877)
* Disallow Messages to simultaneously be "FD" and "remote" (#1049)
-* Speed up interface plugin imports by removing pkg_resources (#1110)
+* Speed up interface plugin imports by avoiding pkg_resources (#1110)
* Allowing for extra config arguments in can.logger (#1142, #1170)
* Add changed byte highlighting to viewer.py (#1159)
* Change DLC to DL in Message.\_\_str\_\_() (#1212)
@@ -180,7 +182,7 @@ Behind the scenes & Quality assurance
* Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651)
* Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870)
* Use [Mergify](https://mergify.com/) (#821, #835, #937)
- * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827)
+ * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with mostly [GitHub Actions](https://docs.github.com/en/actions) (#827, #1224)
* Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950)
* [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces
* Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198)
From a2c012899301f0863dd91e3657960d3cd245be73 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Fri, 4 Feb 2022 20:10:39 +0100
Subject: [PATCH 088/475] Prepare 4.0.0-rc.0 (#1241)
---
can/__init__.py | 2 +-
doc/history.rst | 3 ++-
doc/pycanlib.pml | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/can/__init__.py b/can/__init__.py
index f21ffcc09..037c407f0 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.0.0-dev.2"
+__version__ = "4.0.0-rc.0"
log = logging.getLogger("can")
diff --git a/doc/history.rst b/doc/history.rst
index caed67baa..659f18dda 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -49,7 +49,7 @@ The CANalyst-II interface was contributed by Shaoyu Meng in 2018.
Support for CAN within Python
-----------------------------
-Python natively supports the CAN protocol from version 3.3 on, if running on Linux:
+Python natively supports the CAN protocol from version 3.3 on, if running on Linux (with a sufficiently new kernel):
============== ============================================================== ====
Python version Feature Link
@@ -58,4 +58,5 @@ Python version Feature
3.4 Broadcast Management (BCM) commands are natively supported `Docs `__
3.5 CAN FD support `Docs `__
3.7 Support for CAN ISO-TP `Docs `__
+3.9 Native support for joining CAN filters `Docs `__
============== ============================================================== ====
diff --git a/doc/pycanlib.pml b/doc/pycanlib.pml
index 0ddcf25e5..907fadabb 100644
--- a/doc/pycanlib.pml
+++ b/doc/pycanlib.pml
@@ -1,4 +1,4 @@
-/* This promela model was used to verify the concurrent design of the bus object. */
+/* This promela model was used to verify a past design of the bus object. */
bool lock = false;
From 257d57d44c2b0b308ba3a7d455e50b0b15c81a5b Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Fri, 4 Feb 2022 21:19:02 +0100
Subject: [PATCH 089/475] Fix guaranteed crash when using usb2can (#1249)
---
can/interfaces/usb2can/usb2canInterface.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py
index 258853425..0d3323f9e 100644
--- a/can/interfaces/usb2can/usb2canInterface.py
+++ b/can/interfaces/usb2can/usb2canInterface.py
@@ -103,7 +103,7 @@ def __init__(
self.can = Usb2CanAbstractionLayer(dll)
# get the serial number of the device
- device_id = kwargs.get("serial", d=channel)
+ device_id = kwargs.get("serial", channel)
# search for a serial number if the device_id is None or empty
if not device_id:
From 207be4b9520ef036cb89dd6114f73b162f825e88 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Tue, 8 Feb 2022 10:19:10 +1300
Subject: [PATCH 090/475] Update history & contributors (#1251)
* Adds a CONTRIBUTING.md file which links to our docs
* Remove email addresses from contributors.txt
* Add github handles for new contributors
* Update history.rst
---
CONTRIBUTING.md | 1 +
CONTRIBUTORS.txt | 76 ++++++++++++++++++++++++++++++++++++++++--------
doc/history.rst | 28 ++++++++++++++++++
3 files changed, 93 insertions(+), 12 deletions(-)
create mode 100644 CONTRIBUTING.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..c00e9bd32
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+Please read the [Development - Contributing](https://python-can.readthedocs.io/en/stable/development.html#contributing) guidelines in the documentation site.
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index b446489ae..ae7792e42 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -1,11 +1,13 @@
+https://github.com/hardbyte/python-can/graphs/contributors
+
Ben Powell
-Brian Thorne
+Brian Thorne
Geert Linders
Mark Catley
-Phillip Dixon
+Phillip Dixon
Rose Lu
Karl van Workum
-Albert Bloomfield
+Albert Bloomfield
Sam Bristow
Ethan Zonca
Robert Kaye
@@ -15,18 +17,68 @@ Tynan McAuley
Bruno Pennati
Jack Jester-Weinstein
Joshua Villyard
-Giuseppe Corbelli
+Giuseppe Corbelli
Christian Sandberg
-Eduard Bröcker
+Eduard Bröcker
Boris Wenzlaff
Pierre-Luc Tessier Gagné
-Felix Divo
-Kristian Sloth Lauszus
-Shaoyu Meng
-Alexander Mueller
+Felix Divo
+Kristian Sloth Lauszus
+Shaoyu Meng
+Alexander Mueller
Jan Goeteyn
-"ykzheng"
+ykzheng
Lear Corporation
-Nick Black
+Nick Black
Francisco Javier Burgos Macia
-Felix Nieuwenhuizen
+Felix Nieuwenhuizen
+@marcel-kanter
+@bessman
+@koberbe
+@tamenol
+@deonvdw
+@ptoews
+@chrisoro
+@sou1hacker
+@auden-rovellequartz
+@typecprint
+@tysonite
+@Joey5337
+@Aajn
+@josko7452
+@leventerevesz
+@ericevenchick
+@ixygo
+@Gussy
+@altendky
+@philsc
+@rliebscher
+@jxltom
+@kdschlosser
+@tojoh511
+@s0kr4t3s
+@jaesc
+@NiallDarwin
+@sdorre
+@gordon-epc
+@willson556
+@jjguti
+@wiboticalex
+@illuusio
+@cperkulator
+@simontegelid
+@DawidRosinski
+@fabiocrestani
+@ChrisSweetKT
+@ausserlesh
+@wbarnha
+@projectgus
+@samsmith94
+@alexey
+@MattWoodhead
+@nbusser
+@domologic
+@fjburgos
+@pkess
+@felixn
+@Tbruno25
\ No newline at end of file
diff --git a/doc/history.rst b/doc/history.rst
index 659f18dda..9ae0581b0 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -46,6 +46,34 @@ The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018
The CANalyst-II interface was contributed by Shaoyu Meng in 2018.
+@deonvdw added support for the Robotell interface in 2019.
+
+Felix Divo and Karl Ding added type hints for the core library and many
+interfaces leading up to the 4.0 release.
+
+Eric Evenchick added support for the CANtact devices in 2020.
+
+Felix Divo added an interprocess virtual bus interface in 2020.
+
+@jxltom added the gs_usb interface in 2020 supporting Geschwister Schneider USB/CAN devices
+and bytewerk.org candleLight USB CAN devices such as candlelight, canable, cantact, etc.
+
+@jaesc added the nixnet interface in 2021 supporting NI-XNET devices from National Instruments.
+
+Tuukka Pasanen @illuusio added the neousys interface in 2021.
+
+Francisco Javier Burgos Maciá @fjburgos added ixxat FD support.
+
+@domologic contributed a socketcand interface in 2021.
+
+Felix N @felixn contributed the ETAS interface in 2021.
+
+Felix Divo unified exception handling across every interface in the lead up to
+the 4.0 release.
+
+Felix Divo prepared the python-can 4.0 release.
+
+
Support for CAN within Python
-----------------------------
From 1142299ab5c6d7aa5ecb3d0fba561dc72e9a9f54 Mon Sep 17 00:00:00 2001
From: Simon Kerscher
Date: Tue, 8 Feb 2022 10:01:17 +0100
Subject: [PATCH 091/475] If parsed data has shortened, overwrite end of line
with spaces (#1201)
* If parsed data has shortened, overwrite end of line with spaces
This bug could easily be replicated by running:
`python can_viewer.py -c vcan0 -i socketcan -d "123:>BB"`
In another terminal send:
`cansend vcan0 123#FFFF`
Followed by:
`cansend vcan0 123#0001`
* Fill until end of available line after parsed data
as suggedted by @zariiii9003
---
can/viewer.py | 3 +++
test/test_viewer.py | 17 +++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/can/viewer.py b/can/viewer.py
index 9cd5246fb..a84c865f5 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -302,6 +302,9 @@ def draw_can_bus_message(self, msg, sorting=False):
else:
values_list.append(str(x))
values_string = " ".join(values_list)
+ self.ids[key]["values_string_length"] = len(values_string)
+ values_string += " " * (self.x - len(values_string))
+
self.draw_line(self.ids[key]["row"], 77, values_string, color)
except (ValueError, struct.error):
pass
diff --git a/test/test_viewer.py b/test/test_viewer.py
index f2e3ef0e8..20c3d2faa 100644
--- a/test/test_viewer.py
+++ b/test/test_viewer.py
@@ -188,6 +188,16 @@ def test_send(self):
msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False)
self.can_viewer.bus.send(msg)
+ # Send non-CANopen message with long parsed data length
+ data = [255, 255]
+ msg = can.Message(arbitration_id=0x102, data=data, is_extended_id=False)
+ self.can_viewer.bus.send(msg)
+
+ # Send the same command, but with shorter parsed data length
+ data = [0, 0]
+ msg = can.Message(arbitration_id=0x102, data=data, is_extended_id=False)
+ self.can_viewer.bus.send(msg)
+
# Message with extended id
data = [1, 2, 3, 4, 5, 6, 7, 8]
msg = can.Message(arbitration_id=0x123456, data=data, is_extended_id=True)
@@ -210,6 +220,8 @@ def test_receive(self):
# For converting the EMCY and HEARTBEAT messages
0x080 + 0x01: struct.Struct("ff"),
}
@@ -229,6 +241,11 @@ def test_receive(self):
for col, v in self.stdscr_dummy.draw_buffer[_id["row"]].items():
if col >= 52 + _id["msg"].dlc * 3:
self.assertEqual(v, " ")
+ elif _id["msg"].arbitration_id == 0x102:
+ # Make sure the parsed values have been cleared after the shorted message was send
+ for col, v in self.stdscr_dummy.draw_buffer[_id["row"]].items():
+ if col >= 77 + _id["values_string_length"]:
+ self.assertEqual(v, " ")
elif _id["msg"].arbitration_id == 0x123456:
# Check if the counter is incremented
if _id["dt"] == 0:
From 20b3138ea297c524b476e70c4727226f179ac6eb Mon Sep 17 00:00:00 2001
From: jacobian91
Date: Fri, 11 Feb 2022 12:46:11 -0800
Subject: [PATCH 092/475] Pass flags instead of flags_t type for USB2CAN
(#1252)
* Pass flags instead of flags_t type for USB2CAN
* Remove usb2can unused open arguments for super
Co-authored-by: Jacob Erickson
---
can/interfaces/usb2can/usb2canInterface.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py
index 0d3323f9e..e51d485cd 100644
--- a/can/interfaces/usb2can/usb2canInterface.py
+++ b/can/interfaces/usb2can/usb2canInterface.py
@@ -9,7 +9,6 @@
from can import BusABC, Message, CanInitializationError, CanOperationError
from .usb2canabstractionlayer import Usb2CanAbstractionLayer, CanalMsg, CanalError
from .usb2canabstractionlayer import (
- flags_t,
IS_ERROR_FRAME,
IS_REMOTE_FRAME,
IS_ID_TYPE,
@@ -95,7 +94,7 @@ def __init__(
channel=None,
dll="usb2can.dll",
flags=0x00000008,
- *args,
+ *_,
bitrate=500000,
**kwargs,
):
@@ -118,11 +117,9 @@ def __init__(
self.channel_info = f"USB2CAN device {device_id}"
connector = f"{device_id}; {baudrate}"
- self.handle = self.can.open(connector, flags_t)
+ self.handle = self.can.open(connector, flags)
- super().__init__(
- channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, *args, **kwargs
- )
+ super().__init__(channel=channel, **kwargs)
def send(self, msg, timeout=None):
tx = message_convert_tx(msg)
From f1a012cf88c6845ae9afbde9348ddfa7e67da02a Mon Sep 17 00:00:00 2001
From: Nadhmi JAZI <38762095+jazi007@users.noreply.github.com>
Date: Fri, 11 Feb 2022 21:46:35 +0100
Subject: [PATCH 093/475] [IO][ASC]: fix data length (#1246)
Co-authored-by: Nadhmi JAZI
---
can/io/asc.py | 23 ++++++++++++++++-------
test/logformats_test.py | 4 ++--
2 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/can/io/asc.py b/can/io/asc.py
index f8607d061..c45192f75 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -13,7 +13,7 @@
import logging
from ..message import Message
-from ..util import channel2int
+from ..util import channel2int, len2dlc, dlc2len
from .generic import FileIOMessageWriter, MessageReader
from ..typechecking import StringPathLike
@@ -194,11 +194,20 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag
msg_kwargs["bitrate_switch"] = brs == "1"
msg_kwargs["error_state_indicator"] = esi == "1"
dlc = int(dlc_str, self._converted_base)
- msg_kwargs["dlc"] = dlc
data_length = int(data_length_str)
-
- # CAN remote Frame
- msg_kwargs["is_remote_frame"] = data_length == 0
+ if data_length == 0:
+ # CAN remote Frame
+ msg_kwargs["is_remote_frame"] = True
+ msg_kwargs["dlc"] = dlc
+ else:
+ if dlc2len(dlc) != data_length:
+ logger.warning(
+ "DLC vs Data Length mismatch %d[%d] != %d",
+ dlc,
+ dlc2len(dlc),
+ data_length,
+ )
+ msg_kwargs["dlc"] = data_length
self._process_data_string(data, data_length, msg_kwargs)
@@ -381,8 +390,8 @@ def on_message_received(self, msg: Message) -> None:
symbolic_name="",
brs=1 if msg.bitrate_switch else 0,
esi=1 if msg.error_state_indicator else 0,
- dlc=msg.dlc,
- data_length=len(data),
+ dlc=len2dlc(msg.dlc),
+ data_length=len(msg.data),
data=" ".join(data),
message_duration=0,
message_length=0,
diff --git a/test/logformats_test.py b/test/logformats_test.py
index ffb7a75a3..40ecd8ba3 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -520,7 +520,7 @@ def test_can_fd_message_64(self):
arbitration_id=0x4EE,
is_extended_id=False,
channel=3,
- dlc=0xF,
+ dlc=64,
data=[0xA1, 2, 3, 4] + 59 * [0] + [0x64],
is_fd=True,
error_state_indicator=True,
@@ -529,7 +529,7 @@ def test_can_fd_message_64(self):
timestamp=31.506898,
arbitration_id=0x1C4D80A7,
channel=3,
- dlc=0xF,
+ dlc=64,
data=[0xB1, 2, 3, 4] + 59 * [0] + [0x64],
is_fd=True,
bitrate_switch=True,
From 7fcc813d59fe815ae27db9d1195f8136a5bc6c01 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 11 Feb 2022 21:47:07 +0100
Subject: [PATCH 094/475] bugfix ASCReader (#1257)
---
can/io/asc.py | 128 +++-
test/data/issue_1256.asc | 1461 ++++++++++++++++++++++++++++++++++++++
test/logformats_test.py | 3 +
3 files changed, 1556 insertions(+), 36 deletions(-)
create mode 100644 test/data/issue_1256.asc
diff --git a/can/io/asc.py b/can/io/asc.py
index c45192f75..1a97f6b72 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -34,8 +34,6 @@ class ASCReader(MessageReader):
file: TextIO
- FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y"
-
def __init__(
self,
file: Union[StringPathLike, TextIO],
@@ -60,51 +58,95 @@ def __init__(
self.base = base
self._converted_base = self._check_base(base)
self.relative_timestamp = relative_timestamp
- self.date = None
+ self.date: Optional[str] = None
+ self.start_time = 0.0
# TODO - what is this used for? The ASC Writer only prints `absolute`
- self.timestamps_format = None
- self.internal_events_logged = None
+ self.timestamps_format: Optional[str] = None
+ self.internal_events_logged = False
- def _extract_header(self):
+ def _extract_header(self) -> None:
for line in self.file:
line = line.strip()
- lower_case = line.lower()
- if lower_case.startswith("date"):
- self.date = line[5:]
- elif lower_case.startswith("base"):
- try:
- _, base, _, timestamp_format = line.split()
- except ValueError as exception:
- raise Exception(
- f"Unsupported header string format: {line}"
- ) from exception
+
+ datetime_match = re.match(
+ r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE
+ )
+ base_match = re.match(
+ r"base\s+(?Phex|dec)(?:\s+timestamps\s+"
+ r"(?Pabsolute|relative))?",
+ line,
+ re.IGNORECASE,
+ )
+ comment_match = re.match(r"//.*", line)
+ events_match = re.match(
+ r"(?Pno)?\s*internal\s+events\s+logged", line, re.IGNORECASE
+ )
+
+ if datetime_match:
+ self.date = datetime_match.group("datetime_string")
+ self.start_time = (
+ 0.0
+ if self.relative_timestamp
+ else self._datetime_to_timestamp(self.date)
+ )
+ continue
+
+ elif base_match:
+ base = base_match.group("base")
+ timestamp_format = base_match.group("timestamp_format")
self.base = base
self._converted_base = self._check_base(self.base)
- self.timestamps_format = timestamp_format
- elif lower_case.endswith("internal events logged"):
- self.internal_events_logged = not lower_case.startswith("no")
- elif lower_case.startswith("//"):
- # ignore comments
+ self.timestamps_format = timestamp_format or "absolute"
continue
- # grab absolute timestamp
- elif lower_case.startswith("begin triggerblock"):
- if self.relative_timestamp:
- self.start_time = 0.0
- else:
- try:
- _, _, start_time = lower_case.split(None, 2)
- start_time = datetime.strptime(
- start_time, self.FORMAT_START_OF_FILE_DATE
- ).timestamp()
- except (ValueError, OSError):
- # `OSError` to handle non-POSIX capable timestamps
- start_time = 0.0
- self.start_time = start_time
- # Currently the last line in the header which is parsed
+
+ elif comment_match:
+ continue
+
+ elif events_match:
+ self.internal_events_logged = events_match.group("no_events") is None
break
+
else:
break
+ @staticmethod
+ def _datetime_to_timestamp(datetime_string: str) -> float:
+ # ugly locale independent solution
+ month_map = {
+ "Jan": 1,
+ "Feb": 2,
+ "Mar": 3,
+ "Apr": 4,
+ "May": 5,
+ "Jun": 6,
+ "Jul": 7,
+ "Aug": 8,
+ "Sep": 9,
+ "Oct": 10,
+ "Nov": 11,
+ "Dec": 12,
+ "Mär": 3,
+ "Mai": 5,
+ "Okt": 10,
+ "Dez": 12,
+ }
+ for name, number in month_map.items():
+ datetime_string = datetime_string.replace(name, str(number).zfill(2))
+
+ datetime_formats = (
+ "%m %d %I:%M:%S.%f %p %Y",
+ "%m %d %I:%M:%S %p %Y",
+ "%m %d %H:%M:%S.%f %Y",
+ "%m %d %H:%M:%S %Y",
+ )
+ for format_str in datetime_formats:
+ try:
+ return datetime.strptime(datetime_string, format_str).timestamp()
+ except ValueError:
+ continue
+
+ raise ValueError(f"Incompatible datetime string {datetime_string}")
+
def _extract_can_id(self, str_can_id: str, msg_kwargs: Dict[str, Any]) -> None:
if str_can_id[-1:].lower() == "x":
msg_kwargs["is_extended_id"] = True
@@ -219,6 +261,20 @@ def __iter__(self) -> Generator[Message, None, None]:
for line in self.file:
line = line.strip()
+ trigger_match = re.match(
+ r"begin\s+triggerblock\s+\w+\s+(?P.+)",
+ line,
+ re.IGNORECASE,
+ )
+ if trigger_match:
+ datetime_str = trigger_match.group("datetime_string")
+ self.start_time = (
+ 0.0
+ if self.relative_timestamp
+ else self._datetime_to_timestamp(datetime_str)
+ )
+ continue
+
if not re.match(
r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)",
line,
diff --git a/test/data/issue_1256.asc b/test/data/issue_1256.asc
new file mode 100644
index 000000000..c3eb55199
--- /dev/null
+++ b/test/data/issue_1256.asc
@@ -0,0 +1,1461 @@
+date Tue May 27 04:09:35.000 pm 2014
+base hex timestamps absolute
+internal events logged
+// version 10.0.1
+ 0.019968 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.029964 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.039943 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.039977 1 11 Rx d 8 4A 28 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.049949 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.059945 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.059976 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.060015 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 0.069970 1 11 Rx d 8 84 29 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.070001 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.079951 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.089947 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.099951 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.099982 1 11 Rx d 8 BD 2A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.109949 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.109983 1 10 Rx d 8 10 27 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.110014 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.110032 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.110053 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.129997 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.130036 1 11 Rx d 8 F5 2B 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.139949 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.149954 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.159951 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.159983 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.160095 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 0.160132 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.169955 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.180007 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.189956 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.189991 1 11 Rx d 8 62 2E 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.199956 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.209970 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.210004 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.210026 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.210084 1 10 Rx d 8 F3 28 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.219957 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.219987 1 11 Rx d 8 95 2F 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.229990 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.240004 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.249954 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.249988 1 11 Rx d 8 C7 30 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.259976 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.260138 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 0.260170 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.269974 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.279956 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.279989 1 11 Rx d 8 F6 31 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.289959 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.299963 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.309957 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.309989 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.310012 1 10 Rx d 8 D5 2A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.310075 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.310097 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.319936 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.329956 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.339973 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.339993 1 11 Rx d 8 4B 34 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.349918 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.359922 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.359957 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 0.360032 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.360102 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.360114 1 11 Rx d 8 71 35 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.380012 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.389965 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.399981 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.400017 1 11 Rx d 8 93 36 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.409967 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.410001 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.410024 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.410086 1 10 Rx d 8 B5 2C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.419955 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.429968 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.430001 1 11 Rx d 8 B2 37 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.439986 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.440148 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.459928 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.459970 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.459991 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 0.460048 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.469963 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.479988 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.489926 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.489959 1 11 Rx d 8 E2 39 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.499927 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.509930 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.509963 1 10 Rx d 8 91 2E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.510027 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.510057 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.519946 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.519980 1 11 Rx d 8 F2 3A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.529932 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.540023 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.549926 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.549958 1 11 Rx d 8 FE 3B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.559975 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.560137 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.560200 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 0.560231 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.579988 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.580022 1 11 Rx d 8 05 3D DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.589973 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.599972 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.609972 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.610004 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.610115 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.610151 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.610162 1 10 Rx d 8 69 30 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.610188 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.629991 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.639991 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.640026 1 11 Rx d 8 01 3F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.649975 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.659977 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.660010 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.660144 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 0.669992 1 11 Rx d 8 F6 3F F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.670076 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.680000 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.689982 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.690146 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.690159 1 11 Rx d 8 E5 40 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.709978 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.710013 1 10 Rx d 8 3B 32 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.710079 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.710100 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.720010 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.729980 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.730013 1 11 Rx d 8 CD 41 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.739981 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.749983 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.759997 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.760030 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.760052 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 0.760113 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.769999 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.779998 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.789982 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.790003 1 11 Rx d 8 8A 43 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.799976 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.809974 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.810125 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.810139 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.810168 1 10 Rx d 8 07 34 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.819974 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.820007 1 11 Rx d 8 5D 44 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.829974 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.839975 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.849974 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.850006 1 11 Rx d 8 29 45 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.860028 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.860065 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 0.860138 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.860201 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.880033 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.880170 1 11 Rx d 8 EE 45 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.889961 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.900008 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.909943 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.909976 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.910071 1 10 Rx d 8 CB 35 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 0.910101 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.910113 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 0.919950 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.929950 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.939955 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.940093 1 11 Rx d 8 5F 47 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.940105 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.959949 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.959982 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 0.960053 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 0.969947 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.969981 1 11 Rx d 8 0B 48 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 0.979953 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.990010 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 0.999991 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.000156 1 11 Rx d 8 AF 48 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.009977 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.010014 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.010037 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.010107 1 10 Rx d 8 86 37 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.019975 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.030073 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.030111 1 11 Rx d 8 4B 49 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.040047 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.050030 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.060044 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.060181 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.060194 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 1.060223 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.060283 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.080019 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.089998 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.090034 1 11 Rx d 8 68 4A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.100056 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.110002 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.110041 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.110098 1 10 Rx d 8 37 39 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.110132 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.110145 1 11 Rx d 8 EA 4A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.110155 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.130015 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.140014 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.150012 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.150061 1 11 Rx d 8 62 4B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.150160 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.150173 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.150238 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 1.169969 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.179968 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.180014 1 11 Rx d 8 D1 4B DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.189955 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.200037 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.209998 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.210021 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.210151 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.210196 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.210251 1 10 Rx d 8 DE 3A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.220057 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.230061 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.240062 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.240099 1 11 Rx d 8 93 4C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.250061 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.260053 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.260090 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.260113 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 1.270063 1 11 Rx d 8 E6 4C F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.270179 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.270193 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.289924 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.300009 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.300037 1 11 Rx d 8 2F 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.309979 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.310113 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.310183 1 10 Rx d 8 78 3C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.310217 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.310249 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.329968 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.330021 1 11 Rx d 8 6F 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.339962 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.349962 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.359966 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.359992 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.360009 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 1.360074 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.370015 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.380032 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.390025 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.390058 1 11 Rx d 8 D1 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.400054 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.400094 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.400116 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.400176 1 10 Rx d 8 06 3E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.400209 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.420035 1 11 Rx d 8 F4 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.420068 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.430012 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.439963 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.449976 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.450009 1 11 Rx d 8 0C 4E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.459963 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.459986 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.460057 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 1.469973 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.480036 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.480071 1 11 Rx d 8 1B 4E DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.489976 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.500035 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.509978 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.510010 1 11 Rx d 8 20 4E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.510100 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.510131 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.510142 1 10 Rx d 8 86 3F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.519982 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.530039 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.540022 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.540051 1 11 Rx d 8 1B 4E F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.550046 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.560021 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.560057 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.560181 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 1.570020 1 11 Rx d 8 0C 4E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.570138 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.580022 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.590043 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.600024 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.600055 1 11 Rx d 8 F4 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.610019 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.610179 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.610191 1 10 Rx d 8 F7 40 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.610217 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.620027 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.630024 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.630055 1 11 Rx d 8 D1 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.640032 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.650055 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.650094 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.650119 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.650135 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 1.650190 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.670103 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.680104 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.690059 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.690097 1 11 Rx d 8 6F 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.690212 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.710033 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.710070 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.710139 1 10 Rx d 8 59 42 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.710189 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.720025 1 11 Rx d 8 2F 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.720057 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.730027 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.730186 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.750028 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.750060 1 11 Rx d 8 E6 4C D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.760049 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.760080 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.760095 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 1.770032 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.780053 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.780087 1 11 Rx d 8 93 4C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.790032 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.800013 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.810033 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.810068 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.810178 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.810211 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.810263 1 10 Rx d 8 AB 43 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.810292 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.830028 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.840037 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.840069 1 11 Rx d 8 D1 4B F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.850034 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.860036 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.860070 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.860091 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 1.870040 1 11 Rx d 8 62 4B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.870173 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.880039 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.890037 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.900074 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.900111 1 11 Rx d 8 EA 4A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.900134 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.900151 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.900246 1 10 Rx d 8 EB 44 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 1.900276 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 1.920089 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.930090 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.930127 1 11 Rx d 8 68 4A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.940093 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.940132 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.960058 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.960096 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 1.960118 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 1.960187 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 1.970038 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.980044 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.990002 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 1.990149 1 11 Rx d 8 4B 49 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.000000 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.010062 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.010100 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.010143 1 10 Rx d 8 1A 46 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.010182 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.020003 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.020035 1 11 Rx d 8 AF 48 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.030002 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.040047 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.050003 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.050137 1 11 Rx d 8 0B 48 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.060035 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.060072 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.060166 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 2.070114 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.080034 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.080071 1 11 Rx d 8 5F 47 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.090050 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.100064 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.110044 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.110208 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.110260 1 10 Rx d 8 36 47 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.110289 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.110316 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.110327 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.130053 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.140028 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.140060 1 11 Rx d 8 EE 45 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.150070 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.150102 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.150125 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.150231 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 2.170064 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.170211 1 11 Rx d 8 29 45 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.180053 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.190065 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.200058 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.200095 1 11 Rx d 8 5D 44 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.210053 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.210089 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.210158 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.210227 1 10 Rx d 8 3F 48 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.220056 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.230054 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.230215 1 11 Rx d 8 8A 43 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.230267 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.250051 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.260053 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.260089 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.260111 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.260127 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 2.270057 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.280069 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.290077 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.290234 1 11 Rx d 8 CD 41 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.300073 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.310053 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.310096 1 10 Rx d 8 34 49 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.310167 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.310239 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.310268 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.310280 1 11 Rx d 8 E5 40 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.330060 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.340074 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.350083 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.350234 1 11 Rx d 8 F6 3F D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.350288 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.350300 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 2.350330 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.370081 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.380078 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.380119 1 11 Rx d 8 01 3F DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.390063 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.400062 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.400097 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.400120 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.400136 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.400236 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.400266 1 10 Rx d 8 14 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.420096 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.430063 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.440083 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.440116 1 11 Rx d 8 05 3D F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.440230 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.460066 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.460101 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 2.460172 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.470079 1 11 Rx d 8 FE 3B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.470111 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.480072 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.480232 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.500067 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.500100 1 11 Rx d 8 F2 3A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.510082 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.510117 1 10 Rx d 8 E0 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.510181 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.510237 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.520174 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.520211 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.520234 1 11 Rx d 8 E2 39 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.540030 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.550072 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.560027 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.560060 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.560141 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.560196 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 2.560227 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.580070 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.590075 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.590111 1 11 Rx d 8 B2 37 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.600069 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.610070 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.610104 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.610168 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.610189 1 10 Rx d 8 96 4B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.620075 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.620108 1 11 Rx d 8 93 36 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.630077 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.640065 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.650071 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.650103 1 11 Rx d 8 71 35 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.650124 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.650141 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.650240 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 2.670078 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.680091 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.680126 1 11 Rx d 8 4B 34 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.690092 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.690126 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.710092 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.710128 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.710154 1 10 Rx d 8 37 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.710214 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.710259 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.720071 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.730075 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.730236 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.730256 1 11 Rx d 8 F6 31 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.750078 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.760095 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.760128 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.760150 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 2.770101 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.770139 1 11 Rx d 8 C7 30 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.770253 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.790118 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.800075 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.800113 1 11 Rx d 8 95 2F CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.810084 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.810116 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.810185 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.810272 1 10 Rx d 8 C1 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.810301 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.830087 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.830122 1 11 Rx d 8 62 2E 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.840105 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.850098 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.860076 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.860114 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.860136 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.860153 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 2.870100 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.880131 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.890145 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.890182 1 11 Rx d 8 F5 2B 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.900088 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.900125 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.900150 1 10 Rx d 8 34 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 2.900213 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.900283 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 2.920086 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.920229 1 11 Rx d 8 BD 2A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.930105 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.940094 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.940128 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.940151 1 11 Rx d 8 84 29 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.960131 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.960153 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 2.960199 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 2.970084 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.980091 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 2.980263 1 11 Rx d 8 4A 28 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 2.980317 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.000106 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.010158 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.010195 1 11 Rx d 8 10 27 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.010218 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.010234 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.010278 1 10 Rx d 8 91 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.020129 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.030126 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.040128 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.040267 1 11 Rx d 8 D6 25 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.050098 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.060086 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.060119 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 3.060193 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.060269 1 11 Rx d 8 9C 24 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.060280 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.080052 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.090013 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.100011 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.100139 1 11 Rx d 8 63 23 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.110014 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.110044 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.110104 1 10 Rx d 8 D7 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.110134 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.120011 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.130110 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.130139 1 11 Rx d 8 2B 22 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.140015 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.150013 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.160153 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.160290 1 11 Rx d 8 F4 20 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.160342 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.160395 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 3.170068 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.180094 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.190101 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.190134 1 11 Rx d 8 BE 1F 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.190157 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.210102 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.210135 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.210157 1 10 Rx d 8 06 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.210223 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.220098 1 11 Rx d 8 8B 1E 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.220219 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.230103 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.230137 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.250083 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.250118 1 11 Rx d 8 59 1D D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.260090 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.260122 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 3.260194 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.270103 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.280074 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.280241 1 11 Rx d 8 2A 1C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.290127 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.300105 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.310145 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.310178 1 11 Rx d 8 FE 1A F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.310201 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.310217 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.310282 1 10 Rx d 8 1D 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.310326 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.330117 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.340106 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.340272 1 11 Rx d 8 D5 19 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.350133 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.360104 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.360136 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 3.360206 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.370111 1 11 Rx d 8 AF 18 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.370143 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.380073 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.390099 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.400128 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.400301 1 11 Rx d 8 8D 17 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.400355 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.400367 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.400394 1 10 Rx d 8 1D 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.400422 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.420127 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.430111 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.430143 1 11 Rx d 8 6E 16 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.440133 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.440164 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.460133 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.460291 1 11 Rx d 8 54 15 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.460343 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.460396 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 3.470130 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.480128 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.480160 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.480184 1 11 Rx d 8 3E 14 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.500107 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.510123 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.510160 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.510183 1 10 Rx d 8 06 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.510246 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.520133 1 11 Rx d 8 2E 13 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.520256 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.520336 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.540135 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.550123 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.550154 1 11 Rx d 8 22 12 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.560132 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.560163 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 3.560234 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.560312 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.580132 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.580289 1 11 Rx d 8 1B 11 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.590114 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.600140 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.610137 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.610168 1 11 Rx d 8 1A 10 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.610191 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.610209 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.610267 1 10 Rx d 8 D7 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.620042 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.630075 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.640082 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.640213 1 11 Rx d 8 1F 0F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.650082 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.650118 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.650142 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 3.650202 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.670166 1 11 Rx d 8 2A 0E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.670202 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.680120 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.690128 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.690160 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.690184 1 11 Rx d 8 3B 0D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.710127 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.710271 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.710304 1 10 Rx d 8 91 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.710332 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.720163 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.730129 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.730166 1 11 Rx d 8 53 0C 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.730283 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.750126 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.760173 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.760210 1 11 Rx d 8 71 0B 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.760233 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.760249 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 3.770116 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.770268 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.790150 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.790184 1 11 Rx d 8 96 0A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.800152 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.810126 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.810151 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.810225 1 10 Rx d 8 34 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.810258 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.810288 1 11 Rx d 8 C3 09 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.810300 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.830121 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.840131 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.850150 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.850182 1 11 Rx d 8 F7 08 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.850304 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.850318 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 3.850348 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.870134 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.880155 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.880187 1 11 Rx d 8 32 08 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.890156 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.890314 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.900151 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.900184 1 11 Rx d 8 76 07 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.900206 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.900308 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 3.900337 1 10 Rx d 8 C1 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 3.920150 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.930141 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.940146 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.940177 1 11 Rx d 8 C1 06 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.940289 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.960155 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.960321 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 3.960353 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 3.970143 1 11 Rx d 8 15 06 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 3.970175 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.980155 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 3.980187 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.000161 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.000193 1 11 Rx d 8 71 05 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.010222 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.010262 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.010319 1 10 Rx d 8 37 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.010350 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.020171 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.020337 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.020350 1 11 Rx d 8 D5 04 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.040172 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.050149 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.060159 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.060196 1 11 Rx d 8 42 04 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.060314 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.060367 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 4.060397 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.080148 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.090164 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.090198 1 11 Rx d 8 B8 03 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.100207 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.110147 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.110185 1 10 Rx d 8 96 4B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.110267 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.110291 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.120175 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.120209 1 11 Rx d 8 36 03 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.130179 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.140158 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.140314 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.140327 1 11 Rx d 8 BE 02 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.150166 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.150201 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 4.150271 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.170105 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.180168 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.180203 1 11 Rx d 8 4F 02 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.190075 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.190107 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.210114 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.210252 1 11 Rx d 8 E9 01 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.210265 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.210307 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.210336 1 10 Rx d 8 E0 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.220109 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.230170 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.230205 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.230222 1 11 Rx d 8 8D 01 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.250120 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.260111 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.260144 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 4.260213 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.270185 1 11 Rx d 8 3A 01 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.270317 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.270392 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.290125 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.300176 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.300208 1 11 Rx d 8 F1 00 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.310134 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.310165 1 10 Rx d 8 14 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.310231 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.310286 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.310347 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.330186 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.330343 1 11 Rx d 8 B1 00 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.340151 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.350181 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.360178 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.360210 1 11 Rx d 8 7B 00 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.360232 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.360249 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 4.370158 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.380181 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.390169 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.390287 1 11 Rx d 8 4F 00 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.390342 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.400204 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.400241 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.400310 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.400388 1 10 Rx d 8 34 49 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.420206 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.420243 1 11 Rx d 8 2C 00 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.430163 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.440152 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.440184 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.440208 1 11 Rx d 8 14 00 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.460180 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.460345 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.460388 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 4.470164 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.480182 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.480216 1 11 Rx d 8 05 00 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.480327 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.500107 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.510152 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.510187 1 11 Rx d 8 00 00 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.510205 1 10 Rx d 8 3F 48 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.510259 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.510290 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.520188 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.520347 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.540146 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.540175 1 11 Rx d 8 05 00 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.550167 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.560189 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.560224 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.560343 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 4.560373 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.560385 1 11 Rx d 8 14 00 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.580185 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.590144 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.600191 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.600219 1 11 Rx d 8 2C 00 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.600293 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.600307 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.600335 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.600346 1 10 Rx d 8 36 47 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.620191 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.630170 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.630202 1 11 Rx d 8 4F 00 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.640194 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.640338 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.650180 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.650215 1 11 Rx d 8 7B 00 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.650237 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.650349 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 4.670204 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.680196 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.690196 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.690216 1 11 Rx d 8 B1 00 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.690288 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.710150 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.710187 1 10 Rx d 8 1A 46 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.710208 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.710224 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.720179 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.720293 1 11 Rx d 8 F1 00 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.730133 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.730167 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.750198 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.750232 1 11 Rx d 8 3A 01 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.760140 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.760173 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 4.760240 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.770140 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.770174 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.770197 1 11 Rx d 8 8D 01 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.790183 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.800136 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.810198 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.810231 1 11 Rx d 8 E9 01 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.810306 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.810320 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.810345 1 10 Rx d 8 EB 44 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.810374 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.830181 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.840222 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.840254 1 11 Rx d 8 4F 02 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.850262 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.850399 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.850413 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 4.850442 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.870188 1 11 Rx d 8 BE 02 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.870319 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.880199 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.890186 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.900200 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.900232 1 11 Rx d 8 36 03 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.900255 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.900274 1 10 Rx d 8 AB 43 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 4.900333 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 4.900379 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.920200 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.930189 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.930226 1 11 Rx d 8 B8 03 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.930343 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.940195 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.960198 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.960231 1 11 Rx d 8 42 04 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 4.960253 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 4.960364 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 4.970188 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.980200 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.980360 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 4.980377 1 11 Rx d 8 D5 04 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.000197 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.010190 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.010223 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.010289 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.010310 1 10 Rx d 8 59 42 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.020239 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.020276 1 11 Rx d 8 71 05 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.020367 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.040200 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.050214 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.050249 1 11 Rx d 8 15 06 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.060174 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.060206 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.060338 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 5.060374 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.080201 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.080233 1 11 Rx d 8 C1 06 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.090209 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.100201 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.100357 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.100370 1 11 Rx d 8 76 07 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.100377 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.100404 1 10 Rx d 8 F7 40 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.100433 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.120199 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.130213 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.140201 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.140232 1 11 Rx d 8 32 08 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.140341 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.150214 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.150251 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.150351 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 5.170256 1 11 Rx d 8 F7 08 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.170292 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.180234 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.190222 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.190259 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.190284 1 11 Rx d 8 C3 09 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.210201 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.210237 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.210344 1 10 Rx d 8 86 3F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.210373 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.220196 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.230215 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.230373 1 11 Rx d 8 96 0A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.230426 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.250211 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.260116 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.260139 1 11 Rx d 8 71 0B 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.260154 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 5.260198 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.270165 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.270200 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.290162 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.290299 1 11 Rx d 8 53 0C 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.300164 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.310223 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.310257 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.310301 1 10 Rx d 8 06 3E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.310340 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.310404 1 11 Rx d 8 3B 0D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.310415 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.330168 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.340163 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.350165 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.350300 1 11 Rx d 8 2A 0E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.350353 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.350365 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.350373 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 5.370212 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.380203 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.380237 1 11 Rx d 8 1F 0F DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.390213 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.400203 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.400242 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.400266 1 11 Rx d 8 1A 10 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.400283 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.400347 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.400410 1 10 Rx d 8 78 3C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.420202 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.430213 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.440203 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.440238 1 11 Rx d 8 1B 11 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.440352 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.460203 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.460238 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.460344 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 5.470230 1 11 Rx d 8 22 12 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.470257 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.480180 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.480355 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.500216 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.500252 1 11 Rx d 8 2E 13 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.510235 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.510269 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.510291 1 10 Rx d 8 DE 3A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.510349 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.510403 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.520203 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.520240 1 11 Rx d 8 3E 14 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.540232 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.550219 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.560240 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.560273 1 11 Rx d 8 54 15 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.560385 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 5.560417 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.560477 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.580241 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.590253 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.590290 1 11 Rx d 8 6E 16 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.600261 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.600398 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.600412 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.600439 1 10 Rx d 8 37 39 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.600467 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.620237 1 11 Rx d 8 8D 17 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.620368 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.630250 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.640286 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.640323 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.640346 1 11 Rx d 8 AF 18 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.650275 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.650312 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.650411 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 5.670301 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.680244 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.680281 1 11 Rx d 8 D5 19 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.680398 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.690241 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.710227 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.710266 1 11 Rx d 8 FE 1A F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.710289 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.710355 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.710408 1 10 Rx d 8 86 37 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.720247 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.730242 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.730400 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.730413 1 11 Rx d 8 2A 1C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.750231 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.760248 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.760281 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.760303 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 5.770228 1 11 Rx d 8 59 1D F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.770350 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.770373 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.790239 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.800150 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.800188 1 11 Rx d 8 8B 1E CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.810189 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.810224 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.810337 1 10 Rx d 8 CB 35 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.810367 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.810396 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.830234 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.830270 1 11 Rx d 8 BE 1F 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.840299 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.850194 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.850341 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.850354 1 11 Rx d 8 F4 20 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.850362 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 5.850392 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.870185 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.880196 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.890274 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.890311 1 11 Rx d 8 2B 22 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.890425 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.900288 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.900326 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 5.900394 1 10 Rx d 8 07 34 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 5.900429 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.920258 1 11 Rx d 8 63 23 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.920295 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.930234 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.940252 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.940284 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.940307 1 11 Rx d 8 9C 24 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.960253 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.960285 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 5.960389 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 5.970257 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.980257 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 5.980424 1 11 Rx d 8 D6 25 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 5.980476 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.000260 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.010259 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.010291 1 11 Rx d 8 10 27 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.010316 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.010375 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.010396 1 10 Rx d 8 3B 32 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.010437 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.020264 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.040254 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.040431 1 11 Rx d 8 4A 28 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.050267 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.060287 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.060329 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.060459 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 6.060490 1 11 Rx d 8 84 29 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.060501 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.080264 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.090264 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.100264 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.100426 1 11 Rx d 8 BD 2A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.100478 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.100490 1 10 Rx d 8 69 30 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.100516 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.100527 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.120259 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.130240 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.130278 1 11 Rx d 8 F5 2B 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.140262 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.140294 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.150266 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.150298 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.150320 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 6.150390 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.170276 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.180295 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.180332 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.180356 1 11 Rx d 8 62 2E 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.190293 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.210248 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.210286 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.210397 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.210437 1 10 Rx d 8 91 2E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.220267 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.220301 1 11 Rx d 8 95 2F 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.230228 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.230392 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.250275 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.250312 1 11 Rx d 8 C7 30 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.260277 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.260314 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 6.260390 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.260402 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.270300 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.270338 1 11 Rx d 8 F6 31 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.290288 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.300282 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.310272 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.310305 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.310417 1 10 Rx d 8 B5 2C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.310449 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.310511 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.310539 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.330273 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.340288 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.340319 1 11 Rx d 8 4B 34 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.350201 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.350238 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.350258 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 6.350266 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.370218 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.370357 1 11 Rx d 8 71 35 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.380217 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.390218 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.400189 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.400212 1 11 Rx d 8 93 36 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.400228 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.400243 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.400303 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.400334 1 10 Rx d 8 D5 2A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.420281 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.430259 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.430419 1 11 Rx d 8 B2 37 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.430471 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.440281 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.460278 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.460316 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.460339 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 6.460410 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.470284 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.480284 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.480317 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.480340 1 11 Rx d 8 E2 39 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.500285 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.510262 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.510296 1 10 Rx d 8 F3 28 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.510359 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.510415 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.510427 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.510436 1 11 Rx d 8 F2 3A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.520278 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.540273 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.550261 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.550299 1 11 Rx d 8 FE 3B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.560258 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.560405 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.560469 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 6.560500 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.580285 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.580323 1 11 Rx d 8 05 3D DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.590290 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.600283 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.600320 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.600343 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.600359 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.600416 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.600427 1 10 Rx d 8 10 27 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.620260 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.630273 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.640290 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.640326 1 11 Rx d 8 01 3F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.640439 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.650286 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.650319 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.650449 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 6.670354 1 11 Rx d 8 F6 3F F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.670389 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.680295 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.680459 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.690274 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.690306 1 11 Rx d 8 E5 40 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.710273 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.710311 1 10 Rx d 8 2D 25 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.710380 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.710451 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.720295 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.730276 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.730308 1 11 Rx d 8 CD 41 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.730420 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.750294 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.760296 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.760330 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.760352 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 6.760414 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.760434 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.770281 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.790280 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.790318 1 11 Rx d 8 8A 43 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.800301 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.810282 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.810447 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.810510 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.810539 1 10 Rx d 8 4B 23 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.810567 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.810579 1 11 Rx d 8 5D 44 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.830296 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.840298 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.850278 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.850309 1 11 Rx d 8 29 45 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.850404 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.850417 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 6.850450 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.870283 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.870861 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.870893 1 11 Rx d 8 EE 45 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.890260 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.890295 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.900196 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.900215 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.900225 1 10 Rx d 8 6B 21 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 6.900260 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.900326 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 6.920201 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.930203 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.930351 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.930363 1 11 Rx d 8 5F 47 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.940201 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.960203 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.960231 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 6.960293 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 6.970202 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.970229 1 11 Rx d 8 0B 48 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 6.980204 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 6.980229 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.000335 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.000507 1 11 Rx d 8 AF 48 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.010287 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.010320 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.010343 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.010400 1 10 Rx d 8 8F 1F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.010457 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.020304 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.020339 1 11 Rx d 8 4B 49 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.040311 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.050320 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.060309 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.060477 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.060529 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 7.060560 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.060620 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.080310 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.090297 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.090329 1 11 Rx d 8 68 4A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.100292 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.100312 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.100328 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.100387 1 10 Rx d 8 B7 1D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.100419 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.120312 1 11 Rx d 8 EA 4A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.120444 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.130326 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.140333 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.140370 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.140394 1 11 Rx d 8 62 4B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.150338 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.150376 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.150480 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 7.170318 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.180302 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.180442 1 11 Rx d 8 D1 4B DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.180498 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.190295 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.210313 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.210347 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.210370 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.210430 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.210497 1 10 Rx d 8 E5 1B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.220304 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.230303 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.230336 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.230360 1 11 Rx d 8 93 4C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.250319 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.260301 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.260335 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.260357 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 7.260418 1 11 Rx d 8 E6 4C F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.260497 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.270301 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.290306 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.300306 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.300341 1 11 Rx d 8 2F 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.310325 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.310486 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.310548 1 10 Rx d 8 19 1A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.310577 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.310605 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.330337 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.330375 1 11 Rx d 8 6F 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.340304 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.350330 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.350362 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.350386 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.350404 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 7.350467 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.370323 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.380343 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.390331 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.390368 1 11 Rx d 8 D1 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.390484 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.400313 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.400345 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.400406 1 10 Rx d 8 55 18 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.400462 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.420308 1 11 Rx d 8 F4 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.420344 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.430307 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.430471 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.440263 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.440286 1 11 Rx d 8 0C 4E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.460271 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.460305 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.460395 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 7.470270 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.480274 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.480307 1 11 Rx d 8 1B 4E DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.480398 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.500279 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.510321 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.510356 1 11 Rx d 8 20 4E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.510371 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.510413 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.510426 1 10 Rx d 8 9A 16 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.510455 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.520273 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.540273 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.540306 1 11 Rx d 8 1B 4E F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.550279 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.560279 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.560427 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.560491 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 7.560522 1 11 Rx d 8 0C 4E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.560534 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.580317 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.590327 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.600338 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.600371 1 11 Rx d 8 F4 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.600492 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.600510 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.600518 1 10 Rx d 8 E9 14 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.600546 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.620313 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.630335 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.630369 1 11 Rx d 8 D1 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.640340 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.640372 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.650332 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.650364 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.650386 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 7.650450 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.670400 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.680343 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.680511 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.680525 1 11 Rx d 8 6F 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.690328 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.710318 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.710351 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.710419 1 10 Rx d 8 42 13 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.710479 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.720345 1 11 Rx d 8 2F 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.720376 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.720401 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.730346 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.750372 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.750536 1 11 Rx d 8 E6 4C D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.760370 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.760408 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.760431 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 7.760479 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.770375 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.770413 1 11 Rx d 8 93 4C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.790333 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.800344 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.810334 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.810496 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.810548 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.810577 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.810629 1 10 Rx d 8 A8 11 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.810658 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.830335 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.840350 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.840382 1 11 Rx d 8 D1 4B F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.850350 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.850381 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.850410 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.850429 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102
+ 7.870348 1 11 Rx d 8 62 4B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.870470 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.880353 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.890353 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.890391 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.890416 1 11 Rx d 8 EA 4A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.900349 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.900381 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
+ 7.900501 1 10 Rx d 8 1A 10 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16
+ 7.900533 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101
+ 7.920354 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.930328 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.930488 1 11 Rx d 8 68 4A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.930546 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.940352 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.960354 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100
+ 7.960389 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17
+ 7.960411 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102
+ 7.960498 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 40ecd8ba3..d7fb92f81 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -557,6 +557,9 @@ def test_can_and_canfd_error_frames(self):
def test_ignore_comments(self):
_msg_list = self._read_log_file("logfile.asc")
+ def test_no_triggerblock(self):
+ _msg_list = self._read_log_file("issue_1256.asc")
+
class TestBlfFileFormat(ReaderWriterTest):
"""Tests can.BLFWriter and can.BLFReader.
From cbc5294ac7e4dbbb9b71b202723d80b3ce1d60bf Mon Sep 17 00:00:00 2001
From: Felix Divo
Date: Fri, 18 Feb 2022 23:41:27 +0100
Subject: [PATCH 095/475] Finalize changelog for 4.0.0
---
CHANGELOG.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f57a4b17..86612f9b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -103,6 +103,8 @@ Improved interfaces
* Improve timestamp accuracy on Windows (#934, #936)
* usb2can
* Fix "Error 8" on Windows and provide better error messages (#989)
+ * Fix crash on initialization (#1248, #1249)
+ * Pass flags instead of flags_t type upon initialization (#1252)
* serial
* Fix "TypeError: cannot unpack non-iterable NoneType" and more robust error handling (#1000, #1010)
* canalystii
@@ -122,7 +124,7 @@ Other API changes and improvements
* [Log rotation](https://python-can.readthedocs.io/en/develop/listeners.html#can.SizedRotatingLogger) (#648, #874, #881, #1147)
* Transparent (de)compression of [gzip](https://docs.python.org/3/library/gzip.html) files for all formats (#1221)
* Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783)
- * ASCReader/Writer enhancements like increased robustness (#820, #1223)
+ * ASCReader/Writer enhancements like increased robustness (#820, #1223, #1256, #1257)
* Adding absolute timestamps to ASC reader (#761)
* Support other base number (radix) at ASCReader (#764)
* Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194)
@@ -167,6 +169,7 @@ Other Bugfixes
* Calling stop_all_periodic_tasks() in BusABC.shutdown() and all interfaces call it on shutdown (#1174)
* Timing configurations do not allow int (#1175)
* Some smaller bugfixes are not listed here since the problems were never part of a proper release
+* ASCReader & ASCWriter using DLC as data length (#1245, #1246)
Behind the scenes & Quality assurance
-------------------------------------
From d177a821bb10333aa546e601116f71dbafd11252 Mon Sep 17 00:00:00 2001
From: Felix Divo
Date: Fri, 18 Feb 2022 23:42:26 +0100
Subject: [PATCH 096/475] Update version to 4.0.0
---
can/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/__init__.py b/can/__init__.py
index 037c407f0..2a0b805ac 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.0.0-rc.0"
+__version__ = "4.0.0"
log = logging.getLogger("can")
From 4d9b32c99c333ce9a0cac89afa9aeb324b3b4f7f Mon Sep 17 00:00:00 2001
From: Nadhmi JAZI <38762095+jazi007@users.noreply.github.com>
Date: Tue, 1 Mar 2022 19:15:06 +0100
Subject: [PATCH 097/475] [IO][canutils]: add direction support (#1244)
Co-authored-by: Nadhmi JAZI
---
can/io/canutils.py | 17 ++++++++++++++---
test/test_logger.py | 2 +-
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/can/io/canutils.py b/can/io/canutils.py
index 69793212c..fa96fcb9d 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -51,7 +51,12 @@ def __iter__(self) -> Generator[Message, None, None]:
continue
channel_string: str
- timestamp_string, channel_string, frame = temp.split()
+ if temp[-2:].lower() in (" r", " t"):
+ timestamp_string, channel_string, frame, is_rx_string = temp.split()
+ is_rx = is_rx_string.strip().lower() == "r"
+ else:
+ timestamp_string, channel_string, frame = temp.split()
+ is_rx = True
timestamp = float(timestamp_string[1:-1])
can_id_string, data = frame.split("#", maxsplit=1)
@@ -101,6 +106,7 @@ def __iter__(self) -> Generator[Message, None, None]:
is_extended_id=is_extended,
is_remote_frame=is_remote_frame,
is_fd=is_fd,
+ is_rx=is_rx,
bitrate_switch=brs,
error_state_indicator=esi,
dlc=dlc,
@@ -164,8 +170,13 @@ def on_message_received(self, msg):
else:
framestr += " %03X#" % (msg.arbitration_id)
+ if msg.is_error_frame:
+ eol = "\n"
+ else:
+ eol = " R\n" if msg.is_rx else " T\n"
+
if msg.is_remote_frame:
- framestr += "R\n"
+ framestr += f"R{eol}"
else:
if msg.is_fd:
fd_flags = 0
@@ -174,6 +185,6 @@ def on_message_received(self, msg):
if msg.error_state_indicator:
fd_flags |= CANFD_ESI
framestr += "#%X" % fd_flags
- framestr += "%s\n" % (msg.data.hex().upper())
+ framestr += f"{msg.data.hex().upper()}{eol}"
self.file.write(framestr)
diff --git a/test/test_logger.py b/test/test_logger.py
index 07cf17d37..b694f06bb 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -132,7 +132,7 @@ def test_compressed_logfile(self):
with gzip.open(self.testfile.name, "rt") as testlog:
last_line = testlog.readlines()[-1]
- self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n")
+ self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401 R\n")
def tearDown(self) -> None:
self.testfile.close()
From 56fb131131fcf2a07f4aabc70fba711a7f7843a0 Mon Sep 17 00:00:00 2001
From: Oliver Hartkopp
Date: Wed, 2 Mar 2022 18:01:15 +0100
Subject: [PATCH 098/475] [IO][canutils]: use common CAN interface names in
generated logfile
CAN interfaces in canutils logfiles are usually named 'can0', 'can32' or
'vcan8'. This allows to split logfiles just by performing 'grep' or to
rename CAN interfaces easily with 'sed'.
The current implementation of the canutils log file writer just provides
channel numbers for CAN interfaces. This patch adds the string 'can' to
the channel number to make it look like a usual canutils logfiles that can
be preprocessed as described above.
The string 'can' is added when the provided CAN channel is only a number.
Author: Brian Thorne
Suggested-by: Oliver Hartkopp
Tested-by: Oliver Hartkopp
---
can/io/canutils.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/can/io/canutils.py b/can/io/canutils.py
index fa96fcb9d..0cca82eb8 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -160,6 +160,8 @@ def on_message_received(self, msg):
timestamp = msg.timestamp
channel = msg.channel if msg.channel is not None else self.channel
+ if isinstance(channel, int) or isinstance(channel, str) and channel.isdigit():
+ channel = f"can{channel}"
framestr = "(%f) %s" % (timestamp, channel)
From 8ffdcbcbf3361ac937ab8cf9f5cd6709733c4e16 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 8 Mar 2022 11:16:54 +0100
Subject: [PATCH 099/475] Fix BLF timestamp conversion (#1273)
* fix rounding error
* fix test and add type annotations
---
can/io/blf.py | 29 ++++++++++++++++++-----------
test/logformats_test.py | 13 +++++++++++++
2 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/can/io/blf.py b/can/io/blf.py
index e7be8979f..efeba9488 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -17,7 +17,7 @@
import datetime
import time
import logging
-from typing import List, BinaryIO, Generator, Union
+from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast
from ..message import Message
from ..util import len2dlc, dlc2len, channel2int
@@ -25,6 +25,9 @@
from .generic import FileIOMessageWriter, MessageReader
+TSystemTime = Tuple[int, int, int, int, int, int, int, int]
+
+
class BLFParseError(Exception):
"""BLF file could not be parsed correctly."""
@@ -97,11 +100,11 @@ class BLFParseError(Exception):
TIME_ONE_NANS = 0x00000002
-def timestamp_to_systemtime(timestamp):
+def timestamp_to_systemtime(timestamp: float) -> TSystemTime:
if timestamp is None or timestamp < 631152000:
# Probably not a Unix timestamp
- return (0, 0, 0, 0, 0, 0, 0, 0)
- t = datetime.datetime.fromtimestamp(timestamp)
+ return 0, 0, 0, 0, 0, 0, 0, 0
+ t = datetime.datetime.fromtimestamp(round(timestamp, 3))
return (
t.year,
t.month,
@@ -110,11 +113,11 @@ def timestamp_to_systemtime(timestamp):
t.hour,
t.minute,
t.second,
- int(round(t.microsecond / 1000.0)),
+ t.microsecond // 1000,
)
-def systemtime_to_timestamp(systemtime):
+def systemtime_to_timestamp(systemtime: TSystemTime) -> float:
try:
t = datetime.datetime(
systemtime[0],
@@ -125,7 +128,7 @@ def systemtime_to_timestamp(systemtime):
systemtime[6],
systemtime[7] * 1000,
)
- return time.mktime(t.timetuple()) + systemtime[7] / 1000.0
+ return t.timestamp()
except ValueError:
return 0
@@ -154,8 +157,8 @@ def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
self.file_size = header[10]
self.uncompressed_size = header[11]
self.object_count = header[12]
- self.start_timestamp = systemtime_to_timestamp(header[14:22])
- self.stop_timestamp = systemtime_to_timestamp(header[22:30])
+ self.start_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[14:22]))
+ self.stop_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[22:30]))
# Read rest of header
self.file.read(header[1] - FILE_HEADER_STRUCT.size)
self._tail = b""
@@ -405,8 +408,12 @@ def __init__(
raise BLFParseError("Unexpected file format")
self.uncompressed_size = header[11]
self.object_count = header[12]
- self.start_timestamp = systemtime_to_timestamp(header[14:22])
- self.stop_timestamp = systemtime_to_timestamp(header[22:30])
+ self.start_timestamp: Optional[float] = systemtime_to_timestamp(
+ cast(TSystemTime, header[14:22])
+ )
+ self.stop_timestamp: Optional[float] = systemtime_to_timestamp(
+ cast(TSystemTime, header[22:30])
+ )
# Jump to the end of the file
self.file.seek(0, 2)
else:
diff --git a/test/logformats_test.py b/test/logformats_test.py
index d7fb92f81..eb8984ef6 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -20,6 +20,7 @@
from datetime import datetime
import can
+from can.io import blf
from .data.example_data import (
TEST_MESSAGES_BASE,
@@ -659,6 +660,18 @@ def test_can_error_frame_ext(self):
self.assertMessagesEqual(actual, [expected] * 2)
self.assertEqual(actual[0].channel, expected.channel)
+ def test_timestamp_to_systemtime(self):
+ self.assertAlmostEqual(
+ 1636485425.999,
+ blf.systemtime_to_timestamp(blf.timestamp_to_systemtime(1636485425.998908)),
+ places=3,
+ )
+ self.assertAlmostEqual(
+ 1636485426.0,
+ blf.systemtime_to_timestamp(blf.timestamp_to_systemtime(1636485425.999908)),
+ places=3,
+ )
+
class TestCanutilsFileFormat(ReaderWriterTest):
"""Tests can.CanutilsLogWriter and can.CanutilsLogReader"""
From b92ee5ad25a55e78638306071d65f60de82c46f8 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 9 Mar 2022 08:54:34 +0100
Subject: [PATCH 100/475] Fix channel2int conversion (#1269)
* make regex dot non-greedy
* Update test/test_util.py
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
---
can/util.py | 2 +-
test/test_util.py | 14 +++++++++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/can/util.py b/can/util.py
index 9400259ce..a9d08c469 100644
--- a/can/util.py
+++ b/can/util.py
@@ -292,7 +292,7 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]:
if isinstance(channel, int):
return channel
if isinstance(channel, str):
- match = re.match(r".*(\d+)$", channel)
+ match = re.match(r".*?(\d+)$", channel)
if match:
return int(match.group(1))
return None
diff --git a/test/test_util.py b/test/test_util.py
index 5768da282..e151e3d63 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -3,7 +3,7 @@
import unittest
import warnings
-from can.util import _create_bus_config, _rename_kwargs
+from can.util import _create_bus_config, _rename_kwargs, channel2int
class RenameKwargsTest(unittest.TestCase):
@@ -64,3 +64,15 @@ def test_timing_can_use_int(self):
_create_bus_config({**self.base_config, **timing_conf})
except TypeError as e:
self.fail(e)
+
+
+class TestChannel2Int(unittest.TestCase):
+ def test_channel2int(self) -> None:
+ self.assertEqual(0, channel2int("can0"))
+ self.assertEqual(0, channel2int("vcan0"))
+ self.assertEqual(1, channel2int("vcan1"))
+ self.assertEqual(12, channel2int("vcan12"))
+ self.assertEqual(3, channel2int(3))
+ self.assertEqual(42, channel2int("42"))
+ self.assertEqual(None, channel2int("can"))
+ self.assertEqual(None, channel2int("can0a"))
From ae0bc1746c04b3c1ccd8b56aba99895d50c87d2d Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 9 Mar 2022 21:44:16 +0100
Subject: [PATCH 101/475] Update the black formatter to stable release
It is finally stable and we should therefore use that version from now on.
---
can/interfaces/socketcan/constants.py | 4 ++--
can/interfaces/socketcan/socketcan.py | 2 +-
can/interfaces/systec/ucan.py | 2 +-
can/interfaces/vector/canlib.py | 2 +-
requirements-lint.txt | 2 +-
test/data/example_data.py | 2 +-
test/network_test.py | 2 +-
7 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py
index 37d4847c4..3144a2cfa 100644
--- a/can/interfaces/socketcan/constants.py
+++ b/can/interfaces/socketcan/constants.py
@@ -58,8 +58,8 @@
CANFD_MTU = 72
-STD_ACCEPTANCE_MASK_ALL_BITS = 2 ** 11 - 1
+STD_ACCEPTANCE_MASK_ALL_BITS = 2**11 - 1
MAX_11_BIT_ID = STD_ACCEPTANCE_MASK_ALL_BITS
-EXT_ACCEPTANCE_MASK_ALL_BITS = 2 ** 29 - 1
+EXT_ACCEPTANCE_MASK_ALL_BITS = 2**29 - 1
MAX_29_BIT_ID = EXT_ACCEPTANCE_MASK_ALL_BITS
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index 5355378ab..082bbcf19 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -829,7 +829,7 @@ def _send_periodic_internal(
def _get_next_task_id(self) -> int:
with self._task_id_guard:
- self._task_id = (self._task_id + 1) % (2 ** 32 - 1)
+ self._task_id = (self._task_id + 1) % (2**32 - 1)
return self._task_id
def _get_bcm_socket(self, channel: str) -> socket.socket:
diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py
index 6e150f7b1..a6de4e9f5 100644
--- a/can/interfaces/systec/ucan.py
+++ b/can/interfaces/systec/ucan.py
@@ -120,7 +120,7 @@ def check_result(result, func, arguments):
try:
# Select the proper dll architecture
- lib = WinDLL("usbcan64.dll" if sys.maxsize > 2 ** 32 else "usbcan32.dll")
+ lib = WinDLL("usbcan64.dll" if sys.maxsize > 2**32 else "usbcan32.dll")
# BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p);
UcanSetDebugMode = lib.UcanSetDebugMode
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 4a90ea1bc..9cecaa83d 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -82,7 +82,7 @@ def __init__(
poll_interval: float = 0.01,
receive_own_messages: bool = False,
bitrate: Optional[int] = None,
- rx_queue_size: int = 2 ** 14,
+ rx_queue_size: int = 2**14,
app_name: Optional[str] = "CANalyzer",
serial: Optional[int] = None,
fd: bool = False,
diff --git a/requirements-lint.txt b/requirements-lint.txt
index e9ad9106c..68caecd03 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,5 +1,5 @@
pylint==2.12.2
-black==21.12b0
+black~=22.1.0
mypy==0.931
mypy-extensions==0.4.3
types-setuptools
diff --git a/test/data/example_data.py b/test/data/example_data.py
index d41544334..0fa70993a 100644
--- a/test/data/example_data.py
+++ b/test/data/example_data.py
@@ -179,7 +179,7 @@ def generate_message(arbitration_id):
Generates a new message with the given ID, some random data
and a non-extended ID.
"""
- data = bytearray([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,
diff --git a/test/network_test.py b/test/network_test.py
index a4e40e901..5900cd10f 100644
--- a/test/network_test.py
+++ b/test/network_test.py
@@ -38,7 +38,7 @@ class ControllerAreaNetworkTestCase(unittest.TestCase):
ids = list(range(num_messages))
data = list(
- bytearray([random.randrange(0, 2 ** 8 - 1) for a in range(random.randrange(9))])
+ bytearray([random.randrange(0, 2**8 - 1) for a in range(random.randrange(9))])
for b in range(num_messages)
)
From af55b0a33d30096cb001c168dafb0c3b81ac9fcc Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Mon, 14 Mar 2022 08:34:21 -0400
Subject: [PATCH 102/475] %d format is for a number, not str (#1281)
---
can/interfaces/neousys/neousys.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py
index 05e1a4418..57f947aa4 100644
--- a/can/interfaces/neousys/neousys.py
+++ b/can/interfaces/neousys/neousys.py
@@ -131,7 +131,7 @@ class NeousysCanBitClk(Structure):
NEOUSYS_CANLIB = CDLL("libwdt_dio.so")
logger.info("Loaded Neousys WDT_DIO Can driver")
except OSError as error:
- logger.info("Cannot load Neousys CAN bus dll or shared object: %d", format(error))
+ logger.info("Cannot load Neousys CAN bus dll or shared object: %s", error)
class NeousysBus(BusABC):
From ed6cd668b9d81df27bcc6a747047d9cba228a682 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 20 Apr 2022 09:19:01 +0200
Subject: [PATCH 103/475] fix #1292 (#1293)
---
can/interfaces/iscan.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py
index 0e51ddd12..e76d9d060 100644
--- a/can/interfaces/iscan.py
+++ b/can/interfaces/iscan.py
@@ -187,12 +187,12 @@ class IscanError(CanError):
def __init__(self, function, error_code: int, arguments) -> None:
try:
- description = ": " + self.ERROR_CODES[self.error_code]
+ description = ": " + self.ERROR_CODES[error_code]
except KeyError:
description = ""
super().__init__(
- f"Function {self.function.__name__} failed{description}",
+ f"Function {function.__name__} failed{description}",
error_code=error_code,
)
From fe18a34be7e2c0f8aae073663e4e3dc73f74f47d Mon Sep 17 00:00:00 2001
From: chrisoro <4160557+chrisoro@users.noreply.github.com>
Date: Wed, 20 Apr 2022 13:27:16 +0200
Subject: [PATCH 104/475] add missing vector devices (#1296)
---
can/interfaces/vector/xldefine.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py
index be40a15a9..032f08318 100644
--- a/can/interfaces/vector/xldefine.py
+++ b/can/interfaces/vector/xldefine.py
@@ -283,9 +283,11 @@ class XL_HardwareType(IntEnum):
XL_HWTYPE_VN1640 = 59
XL_HWTYPE_VN8970 = 61
XL_HWTYPE_VN1611 = 63
+ XL_HWTYPE_VN5240 = 64
XL_HWTYPE_VN5610 = 65
XL_HWTYPE_VN5620 = 66
XL_HWTYPE_VN7570 = 67
+ XL_HWTYPE_VN5650 = 68
XL_HWTYPE_IPCLIENT = 69
XL_HWTYPE_IPSERVER = 71
XL_HWTYPE_VX1121 = 73
From 638d81ac361aef9334b14748a02667d53bf9c987 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 29 Apr 2022 13:08:40 +0200
Subject: [PATCH 105/475] Update black to fix CI
---
requirements-lint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 68caecd03..f62eeb189 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,5 +1,5 @@
pylint==2.12.2
-black~=22.1.0
+black~=22.3.0
mypy==0.931
mypy-extensions==0.4.3
types-setuptools
From 19cf49a728e8b0b0818336c298a6a7bcc1f0e0c7 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Sat, 30 Apr 2022 01:22:49 +0200
Subject: [PATCH 106/475] Fix timestamp handling in udp_multicast on macOS
(#1278)
* Fix timestamp handling in udp_multicast on macOS
* Fix import on Windows
* Fix test conditions for BasicTestUdpMulticastBusIPv4/6
* Fix attribute init
* Fix exceptions (don't use asserts, always raise CAN errors)
---
can/interfaces/udp_multicast/bus.py | 95 ++++++++++++++++++++++-------
doc/interfaces/udp_multicast.rst | 2 +-
test/back2back_test.py | 6 +-
3 files changed, 76 insertions(+), 27 deletions(-)
diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py
index 8fc286627..6b7e57bd9 100644
--- a/can/interfaces/udp_multicast/bus.py
+++ b/can/interfaces/udp_multicast/bus.py
@@ -1,8 +1,14 @@
+import errno
import logging
import select
import socket
import struct
+try:
+ from fcntl import ioctl
+except ModuleNotFoundError: # Missing on Windows
+ pass
+
from typing import List, Optional, Tuple, Union
log = logging.getLogger(__name__)
@@ -21,6 +27,7 @@
# Additional constants for the interaction with Unix kernels
SO_TIMESTAMPNS = 35
+SIOCGSTAMP = 0x8906
class UdpMulticastBus(BusABC):
@@ -174,6 +181,9 @@ def __init__(
self.hop_limit = hop_limit
self.max_buffer = max_buffer
+ # `False` will always work, no matter the setup. This might be changed by _create_socket().
+ self.timestamp_nanosecond = False
+
# Look up multicast group address in name server and find out IP version of the first suitable target
# and then get the address family of it (socket.AF_INET or socket.AF_INET6)
connection_candidates = socket.getaddrinfo( # type: ignore
@@ -200,8 +210,15 @@ def __init__(
# used in recv()
self.received_timestamp_struct = "@ll"
- ancillary_data_size = struct.calcsize(self.received_timestamp_struct)
- self.received_ancillary_buffer_size = socket.CMSG_SPACE(ancillary_data_size)
+ self.received_timestamp_struct_size = struct.calcsize(
+ self.received_timestamp_struct
+ )
+ if self.timestamp_nanosecond:
+ self.received_ancillary_buffer_size = socket.CMSG_SPACE(
+ self.received_timestamp_struct_size
+ )
+ else:
+ self.received_ancillary_buffer_size = 0
# used by send()
self._send_destination = (self.group, self.port)
@@ -238,7 +255,15 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# set how to receive timestamps
- sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
+ try:
+ sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
+ except OSError as error:
+ if error.errno == errno.ENOPROTOOPT: # It is unavailable on macOS
+ self.timestamp_nanosecond = False
+ else:
+ raise error
+ else:
+ self.timestamp_nanosecond = True
# Bind it to the port (on any interface)
sock.bind(("", self.port))
@@ -272,18 +297,22 @@ def send(self, data: bytes, timeout: Optional[float] = None) -> None:
:param timeout: the timeout in seconds after which an Exception is raised is sending has failed
:param data: the data to be sent
- :raises OSError: if an error occurred while writing to the underlying socket
- :raises socket.timeout: if the timeout ran out before sending was completed (this is a subclass of
- *OSError*)
+ :raises can.CanOperationError: if an error occurred while writing to the underlying socket
+ :raises can.CanTimeoutError: if the timeout ran out before sending was completed
"""
if timeout != self._last_send_timeout:
self._last_send_timeout = timeout
# this applies to all blocking calls on the socket, but sending is the only one that is blocking
self._socket.settimeout(timeout)
- bytes_sent = self._socket.sendto(data, self._send_destination)
- if bytes_sent < len(data):
- raise socket.timeout()
+ try:
+ bytes_sent = self._socket.sendto(data, self._send_destination)
+ if bytes_sent < len(data):
+ raise TimeoutError()
+ except TimeoutError:
+ raise can.CanTimeoutError() from None
+ except OSError as error:
+ raise can.CanOperationError("failed to send via socket") from error
def recv(
self, timeout: Optional[float] = None
@@ -320,21 +349,41 @@ def recv(
self.max_buffer, self.received_ancillary_buffer_size
)
- # fetch timestamp; this is configured in in _create_socket()
- assert len(ancillary_data) == 1, "only requested a single extra field"
- cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
- assert (
- cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS
- ), "received control message type that was not requested"
- # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
- seconds, nanoseconds = struct.unpack(
- self.received_timestamp_struct, cmsg_data
- )
- if nanoseconds >= 1e9:
- raise can.CanError(
- f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
+ # fetch timestamp; this is configured in _create_socket()
+ if self.timestamp_nanosecond:
+ # Very similar to timestamp handling in can/interfaces/socketcan/socketcan.py -> capture_message()
+ if len(ancillary_data) != 1:
+ raise can.CanOperationError(
+ "Only requested a single extra field but got a different amount"
+ )
+ cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
+ if cmsg_level != socket.SOL_SOCKET or cmsg_type != SO_TIMESTAMPNS:
+ raise can.CanOperationError(
+ "received control message type that was not requested"
+ )
+ # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
+ seconds, nanoseconds = struct.unpack(
+ self.received_timestamp_struct, cmsg_data
+ )
+ if nanoseconds >= 1e9:
+ raise can.CanOperationError(
+ f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
+ )
+ timestamp = seconds + nanoseconds * 1.0e-9
+ else:
+ result_buffer = ioctl(
+ self._socket.fileno(),
+ SIOCGSTAMP,
+ bytes(self.received_timestamp_struct_size),
+ )
+ seconds, microseconds = struct.unpack(
+ self.received_timestamp_struct, result_buffer
)
- timestamp = seconds + nanoseconds * 1.0e-9
+ if microseconds >= 1e6:
+ raise can.CanOperationError(
+ f"Timestamp microseconds field was out of range: {microseconds} not less than 1e6"
+ )
+ timestamp = seconds + microseconds * 1e-6
return raw_message_data, sender_address, timestamp
diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst
index be5882f20..f2775727c 100644
--- a/doc/interfaces/udp_multicast.rst
+++ b/doc/interfaces/udp_multicast.rst
@@ -25,7 +25,7 @@ for specifying multicast IP addresses.
Supported Platforms
-------------------
-It should work on most Unix systems (including Linux with kernel 2.6.22+) but currently not on Windows.
+It should work on most Unix systems (including Linux with kernel 2.6.22+ and macOS) but currently not on Windows.
Example
-------
diff --git a/test/back2back_test.py b/test/back2back_test.py
index 479274343..b5ae4e27c 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -285,7 +285,7 @@ class BasicTestSocketCan(Back2BackTestCase):
# this doesn't even work on Travis CI for macOS; for example, see
# https://travis-ci.org/github/hardbyte/python-can/jobs/745389871
@unittest.skipUnless(
- IS_UNIX and not IS_OSX,
+ IS_UNIX and not (IS_CI and IS_OSX),
"only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv4(Back2BackTestCase):
@@ -303,8 +303,8 @@ def test_unique_message_instances(self):
# this doesn't even work for loopback multicast addresses on Travis CI; for example, see
# https://travis-ci.org/github/hardbyte/python-can/builds/745065503
@unittest.skipUnless(
- IS_UNIX and not (IS_TRAVIS or IS_OSX),
- "only supported on Unix systems (but not on Travis CI; and not an macOS at GitHub Actions)",
+ IS_UNIX and not (IS_TRAVIS or (IS_CI and IS_OSX)),
+ "only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv6(Back2BackTestCase):
From 1693c27ea751958260a9c162727af1790c28b006 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 30 Apr 2022 21:34:20 +0200
Subject: [PATCH 107/475] test python 3.11 (#1302)
---
.github/workflows/build.yml | 14 +++++++-------
test/test_viewer.py | 15 ++++++++-------
tox.ini | 5 +++--
3 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 993247868..7e37b4ad5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,16 +15,16 @@ jobs:
experimental: [false]
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
# Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196
- # include:
+ include:
# Only test on a single configuration while there are just pre-releases
- # - os: ubuntu-latest
- # experimental: true
- # python-version: "3.11.0-alpha.3"
+ - os: ubuntu-latest
+ experimental: true
+ python-version: "3.11.0-alpha - 3.11.0"
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -44,7 +44,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
@@ -78,7 +78,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
diff --git a/test/test_viewer.py b/test/test_viewer.py
index 20c3d2faa..5633a3bc1 100644
--- a/test/test_viewer.py
+++ b/test/test_viewer.py
@@ -37,7 +37,7 @@
import can
from can.viewer import CanViewer, parse_args
-
+from test.config import IS_CI
# Allow the curses module to be missing (e.g. on PyPy on Windows)
try:
@@ -251,9 +251,10 @@ def test_receive(self):
if _id["dt"] == 0:
self.assertEqual(_id["count"], 1)
else:
- self.assertTrue(
- pytest.approx(_id["dt"], 0.1)
- ) # dt should be ~0.1 s
+ if not IS_CI: # do not test timing in CI
+ assert _id["dt"] == pytest.approx(
+ 0.1, abs=5e-2
+ ) # dt should be ~0.1 s
self.assertEqual(_id["count"], 2)
else:
# Make sure dt is 0
@@ -347,7 +348,7 @@ def test_pack_unpack(self):
raw_data = self.pack_data(CANOPEN_TPDO2 + 1, data_structs, 12.34, 4.5, 6)
parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 1, data_structs, raw_data)
- self.assertTrue(pytest.approx(parsed_data, [12.34, 4.5, 6]))
+ assert parsed_data == pytest.approx([12.34, 4.5, 6])
self.assertTrue(
isinstance(parsed_data[0], float)
and isinstance(parsed_data[1], float)
@@ -356,14 +357,14 @@ def test_pack_unpack(self):
raw_data = self.pack_data(CANOPEN_TPDO3 + 1, data_structs, 123.45, 67.89)
parsed_data = CanViewer.unpack_data(CANOPEN_TPDO3 + 1, data_structs, raw_data)
- self.assertTrue(pytest.approx(parsed_data, [123.45, 67.89]))
+ assert parsed_data == pytest.approx([123.45, 67.89])
self.assertTrue(all(isinstance(d, float) for d in parsed_data))
raw_data = self.pack_data(
CANOPEN_TPDO4 + 1, data_structs, math.pi / 2.0, math.pi
)
parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 1, data_structs, raw_data)
- self.assertTrue(pytest.approx(parsed_data, [math.pi / 2.0, math.pi]))
+ assert parsed_data == pytest.approx([math.pi / 2.0, math.pi])
self.assertTrue(all(isinstance(d, float) for d in parsed_data))
raw_data = self.pack_data(CANOPEN_TPDO1 + 2, data_structs)
diff --git a/tox.ini b/tox.ini
index 6b407dfeb..248a6fd37 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,12 @@
[tox]
+isolated_build = true
[testenv]
deps =
- pytest==6.2.*,>=6.2.5
+ pytest==7.1.*,>=7.1.2
pytest-timeout==2.0.2
pytest-cov==3.0.0
- coverage==6.2
+ coverage==6.3
codecov==2.1.12
hypothesis~=6.35.0
pyserial~=3.5
From 796b52586a5d891e4e2077b423de40cba07f8b44 Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Mon, 2 May 2022 18:35:29 -0400
Subject: [PATCH 108/475] Default mode for FileIOMessageWriter should be wt
(#1303)
Default mode for FileIOMessageWriter should be `wt` instead of `rt`.
---
can/io/generic.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/io/generic.py b/can/io/generic.py
index 6f18fbe65..b45e4dd1f 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -82,7 +82,7 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
file: can.typechecking.FileLike
- def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None:
+ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> None:
# Not possible with the type signature, but be verbose for user-friendliness
if file is None:
raise ValueError("The given file cannot be None")
From 4cb2f2f6fbf6bd378a4e804b5c737a6128caea1d Mon Sep 17 00:00:00 2001
From: Gonzalo Ribera
Date: Wed, 25 May 2022 06:05:36 -0300
Subject: [PATCH 109/475] Fix fileno error on Windows (robotell bus) (#1313)
* Fix fileno error on Windows (robotell bus)
* Fix format
* Add test
* format
---
can/interfaces/robotell.py | 11 ++++++++---
test/test_robotell.py | 4 ++++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py
index 88cee26d1..709fad78d 100644
--- a/can/interfaces/robotell.py
+++ b/can/interfaces/robotell.py
@@ -2,6 +2,7 @@
Interface for Chinese Robotell compatible interfaces (win32/linux).
"""
+import io
import time
import logging
@@ -367,10 +368,14 @@ def shutdown(self):
self.serialPortOrig.close()
def fileno(self):
- if hasattr(self.serialPortOrig, "fileno"):
+ try:
return self.serialPortOrig.fileno()
- # Return an invalid file descriptor on Windows
- return -1
+ except io.UnsupportedOperation:
+ raise NotImplementedError(
+ "fileno is not implemented using current CAN bus on this platform"
+ )
+ except Exception as exception:
+ raise CanOperationError("Cannot fetch fileno") from exception
def get_serial_number(self, timeout):
"""Get serial number of the slcan interface.
diff --git a/test/test_robotell.py b/test/test_robotell.py
index 86e053f2d..8250b7ada 100644
--- a/test/test_robotell.py
+++ b/test/test_robotell.py
@@ -940,6 +940,10 @@ def test_set_hw_filter(self):
),
)
+ def test_when_no_fileno(self):
+ with self.assertRaises(NotImplementedError):
+ self.bus.fileno()
+
if __name__ == "__main__":
unittest.main()
From 97302b9db9ff621a97fbe7a280ec12a261bbff20 Mon Sep 17 00:00:00 2001
From: Jurgis
Date: Wed, 1 Jun 2022 10:13:12 +0300
Subject: [PATCH 110/475] Improve gs_usb usability and fix loopback frames
(#1270)
* Improve gs_usb usability and fix loopback frames
* Fix extended id parsing
* Fix formatting
---
can/interfaces/gs_usb.py | 39 ++++++++++++++++++++++++++++++++-------
doc/interfaces/gs_usb.rst | 10 +++++++++-
2 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py
index 7731d797d..185d28acf 100644
--- a/can/interfaces/gs_usb.py
+++ b/can/interfaces/gs_usb.py
@@ -1,7 +1,7 @@
from typing import Optional, Tuple
from gs_usb.gs_usb import GsUsb
-from gs_usb.gs_usb_frame import GsUsbFrame
+from gs_usb.gs_usb_frame import GsUsbFrame, GS_USB_NONE_ECHO_ID
from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC
import can
import usb
@@ -14,17 +14,42 @@
class GsUsbBus(can.BusABC):
- def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs):
+ def __init__(
+ self,
+ channel,
+ bitrate,
+ index=None,
+ bus=None,
+ address=None,
+ can_filters=None,
+ **kwargs,
+ ):
"""
:param channel: usb device name
+ :param index: device number if using automatic scan, starting from 0.
+ If specified, bus/address shall not be provided.
:param bus: number of the bus that the device is connected to
:param address: address of the device on the bus it is connected to
:param can_filters: not supported
:param bitrate: CAN network bandwidth (bits/s)
"""
- gs_usb = GsUsb.find(bus=bus, address=address)
- if not gs_usb:
- raise CanInitializationError(f"Cannot find device {channel}")
+ if (index is not None) and ((bus or address) is not None):
+ raise CanInitializationError(
+ f"index and bus/address cannot be used simultaneously"
+ )
+
+ if index is not None:
+ devs = GsUsb.scan()
+ if len(devs) <= index:
+ raise CanInitializationError(
+ f"Cannot find device {index}. Devices found: {len(devs)}"
+ )
+ gs_usb = devs[index]
+ else:
+ gs_usb = GsUsb.find(bus=bus, address=address)
+ if not gs_usb:
+ raise CanInitializationError(f"Cannot find device {channel}")
+
self.gs_usb = gs_usb
self.channel_info = channel
@@ -100,13 +125,13 @@ def _recv_internal(
msg = can.Message(
timestamp=frame.timestamp,
arbitration_id=frame.arbitration_id,
- is_extended_id=frame.can_dlc,
+ is_extended_id=frame.is_extended_id,
is_remote_frame=frame.is_remote_frame,
is_error_frame=frame.is_error_frame,
channel=self.channel_info,
dlc=frame.can_dlc,
data=bytearray(frame.data)[0 : frame.can_dlc],
- is_rx=True,
+ is_rx=frame.echo_id == GS_USB_NONE_ECHO_ID,
)
return msg, False
diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst
index aae6c39f5..232786fb7 100755
--- a/doc/interfaces/gs_usb.rst
+++ b/doc/interfaces/gs_usb.rst
@@ -7,7 +7,15 @@ Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schne
Install: ``pip install "python-can[gs_usb]"``
-Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` as shown below:
+Usage: pass device ``index`` (starting from 0) if using automatic device detection:
+
+::
+
+ import can
+
+ bus = can.Bus(bustype="gs_usb", channel=dev.product, index=0, bitrate=250000)
+
+Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below:
::
From 47f673b47dcffa335ee10016052475f8af534b3d Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Wed, 1 Jun 2022 03:03:15 -0500
Subject: [PATCH 111/475] Remove redundant writer.stop call that throws error
(#1317)
* Remove redundant writer.stop call that throws error
The issue is that the writer has already previously been told to stop.
* Remove commented lines and update docstring
The _get_new_writer function was previously calling
`self._writer.close()`. The `self.writer` function is already being
closed in the call stack. Due to `self.writer` and `self._writer`
being linked, there was an error that the IO file was closed.
The `self._writer.close()` was commented out in a prior commit as a
proposal for the change. That comment is now removed. The docstring
is updated.
---
can/io/logger.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index cbed054ac..37ba1e736 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -208,7 +208,10 @@ def on_message_received(self, msg: Message) -> None:
self.writer.on_message_received(msg)
def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
- """Instantiate a new writer after stopping the old one.
+ """Instantiate a new writer.
+
+ .. note::
+ The :attr:`self.writer` should be closed prior to calling this function.
:param filename:
Path-like object that specifies the location and name of the log file.
@@ -216,9 +219,6 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
:return:
An instance of a writer class.
"""
- # Close the old writer first
- if self._writer is not None:
- self._writer.stop()
logger = Logger(filename, *self.writer_args, **self.writer_kwargs)
if isinstance(logger, FileIOMessageWriter):
From 0d7f65f74b8b015fb650e9910a920323023f310d Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 1 Jun 2022 16:07:11 +0200
Subject: [PATCH 112/475] Clean up comment after !1302 (#1322)
---
.github/workflows/build.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7e37b4ad5..5365620cd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,7 +14,6 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
experimental: [false]
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
- # Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196
include:
# Only test on a single configuration while there are just pre-releases
- os: ubuntu-latest
From 0c4dee01a00a9bdd82b71071627b8f2fd2fc929d Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 7 Jun 2022 08:49:57 +0200
Subject: [PATCH 113/475] fix #1299 (#1301)
---
can/io/asc.py | 4 ++--
test/data/issue_1299.asc | 11 +++++++++++
test/logformats_test.py | 3 +++
3 files changed, 16 insertions(+), 2 deletions(-)
create mode 100644 test/data/issue_1299.asc
diff --git a/can/io/asc.py b/can/io/asc.py
index 1a97f6b72..3a320f007 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -202,9 +202,9 @@ def _process_classic_can_frame(
_, dlc_str = rest_of_message.split(None, 1)
data = ""
- dlc = int(dlc_str, self._converted_base)
+ dlc = dlc2len(int(dlc_str, self._converted_base))
msg_kwargs["dlc"] = dlc
- self._process_data_string(data, dlc, msg_kwargs)
+ self._process_data_string(data, min(8, dlc), msg_kwargs)
return Message(**msg_kwargs)
diff --git a/test/data/issue_1299.asc b/test/data/issue_1299.asc
new file mode 100644
index 000000000..43c29302e
--- /dev/null
+++ b/test/data/issue_1299.asc
@@ -0,0 +1,11 @@
+date Thu Apr 28 10:44:52.480 am 2022
+base hex timestamps absolute
+internal events logged
+// version 12.0.0
+Begin TriggerBlock Thu Apr 28 10:44:52.480 am 2022
+ 0.000000 Start of measurement
+ 13.258199 1 180 Tx d 8 6A 00 00 00 00 00 00 00 Length = 244016 BitCount = 125 ID = 384
+ 13.258433 1 221 Tx d 8 C2 4A 05 81 00 00 15 10 Length = 228016 BitCount = 117 ID = 545
+ 13.258671 1 3FF Tx d D 55 AA 01 02 03 04 05 06 Length = 232016 BitCount = 119 ID = 1023
+ 13.258907 1 F4 Tx d 8 8A 1A 0D F2 13 00 00 07 Length = 230016 BitCount = 118 ID = 244
+End TriggerBlock
diff --git a/test/logformats_test.py b/test/logformats_test.py
index eb8984ef6..49e9c563e 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -561,6 +561,9 @@ def test_ignore_comments(self):
def test_no_triggerblock(self):
_msg_list = self._read_log_file("issue_1256.asc")
+ def test_can_dlc_greater_than_8(self):
+ _msg_list = self._read_log_file("issue_1299.asc")
+
class TestBlfFileFormat(ReaderWriterTest):
"""Tests can.BLFWriter and can.BLFReader.
From 14301543424a490d1607b2d0ed56c059cdff3d09 Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Fri, 10 Jun 2022 02:28:06 -0500
Subject: [PATCH 114/475] Enhance `can.logger` to consider the `append` option
(#1327)
* Add passage of **options to the logger initialization
* Add -a boolean option to the parser
* Add manual arg parse for boolean variables
This solution is only required until Python>=3.9.
* Fix the index-error check
* Bolster help documentation for new -a option
* Clean up variable names, notes, etc.
* Whitespace formatting (PEP-8 style)
* Make formatting recomendations based on `pylint logger.py`
* Format code with black logger.py to stop failing the `black` check
* Reduce complexity of append arg parse incorporation
* Fix format with `black can/logger.py`
* Change append argparse access from `results.a` to `results.append`
* Add `dest` option to append argument
---
can/logger.py | 41 +++++++++++++++++++++++++++--------------
1 file changed, 27 insertions(+), 14 deletions(-)
diff --git a/can/logger.py b/can/logger.py
index 053001968..3594427e6 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -31,9 +31,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"-c",
"--channel",
- help='''Most backend interfaces require some sort of channel.
- For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0"
- With the socketcan interfaces valid channel examples include: "can0", "vcan0"''',
+ help=r"Most backend interfaces require some sort of channel. For "
+ r"example with the serial interface the channel might be a rfcomm"
+ r' device: "/dev/rfcomm0". With the socketcan interface valid '
+ r'channel examples include: "can0", "vcan0".',
)
parser.add_argument(
@@ -60,11 +61,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"extra_args",
nargs=argparse.REMAINDER,
- help="""\
- The remaining arguments will be used for the interface initialisation.
- For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent to
- opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')`
- """,
+ help=r"The remaining arguments will be used for the interface "
+ r"initialisation. For example, `-i vector -c 1 --app-name="
+ r"MyCanApp` is the equivalent to opening the bus with `Bus("
+ r"'vector', channel=1, app_name='MyCanApp')",
)
@@ -82,8 +82,10 @@ def _append_filter_argument(
*args,
"--filter",
help="R|Space separated CAN filters for the given CAN interface:"
- "\n : (matches when & mask == can_id & mask)"
- "\n ~ (matches when & mask != can_id & mask)"
+ "\n : (matches when & mask =="
+ " can_id & mask)"
+ "\n ~ (matches when & mask !="
+ " can_id & mask)"
"\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:"
"\n python -m can.viewer -f 100:7FC 200:7F0"
"\nNote that the ID and mask are always interpreted as hex values",
@@ -141,7 +143,8 @@ def _parse_additonal_config(unknown_args):
def main() -> None:
parser = argparse.ArgumentParser(
- description="Log CAN traffic, printing messages to stdout or to a given file.",
+ description="Log CAN traffic, printing messages to stdout or to a "
+ "given file.",
)
_create_base_argument_parser(parser)
@@ -154,12 +157,21 @@ def main() -> None:
default=None,
)
+ parser.add_argument(
+ "-a",
+ "--append",
+ dest="append",
+ help="Append to the log file if it already exists.",
+ action="store_true",
+ )
+
parser.add_argument(
"-s",
"--file_size",
dest="file_size",
type=int,
- help="Maximum file size in bytes. Rotate log file when size threshold is reached.",
+ help="Maximum file size in bytes. Rotate log file when size threshold "
+ "is reached.",
default=None,
)
@@ -201,12 +213,13 @@ def main() -> None:
print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}")
print(f"Can Logger (Started on {datetime.now()})")
+ options = {"append": results.append}
if results.file_size:
logger = SizedRotatingLogger(
- base_filename=results.log_file, max_bytes=results.file_size
+ base_filename=results.log_file, max_bytes=results.file_size, **options
)
else:
- logger = Logger(filename=results.log_file) # type: ignore
+ logger = Logger(filename=results.log_file, **options) # type: ignore
try:
while True:
From cb3d4dcce7794ab3a22cf8f1e69aea33fa837c15 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 20 Jun 2022 14:12:08 +0200
Subject: [PATCH 115/475] fix broken badges
---
README.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.rst b/README.rst
index 956033de1..0e5d79e3f 100644
--- a/README.rst
+++ b/README.rst
@@ -33,15 +33,15 @@ python-can
:target: https://github.com/hardbyte/python-can/actions/workflows/build.yml
:alt: Github Actions workflow status
-.. |build_travis| image:: https://img.shields.io/travis/com/hardbyte/python-can/develop.svg?label=Travis%20CI
- :target: https://travis-ci.com/hardbyte/python-can
+.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI
+ :target: https://app.travis-ci.com/github/hardbyte/python-can
:alt: Travis CI Server for develop branch
.. |coverage| image:: https://codecov.io/gh/hardbyte/python-can/branch/develop/graph/badge.svg
:target: https://codecov.io/gh/hardbyte/python-can/branch/develop
:alt: Test coverage reports on Codecov.io
-.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat
+.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat
:target: https://mergify.io
:alt: Mergify Status
From ec064529b8acffe7f6d9a3494442b6b1c6152e97 Mon Sep 17 00:00:00 2001
From: MattWoodhead
Date: Wed, 22 Jun 2022 09:38:37 +0100
Subject: [PATCH 116/475] Fix fileno error on Windows (Serial bus) (#1333)
* Add conda-forge badge to readme
* Update fileno method in serial_can.py
Change implemented similar to that made in #1313. This means notifiers will now work with the Serial interface.
* Format serial_test.py with black
* Revert "Add conda-forge badge to readme"
This reverts commit 59d0b3cbf876a16ae4781e607071665a2df7b7a4.
---
can/interfaces/serial/serial_can.py | 11 ++++++++---
test/serial_test.py | 18 ++++++++++++++++++
2 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py
index c214d8559..ec4bb8671 100644
--- a/can/interfaces/serial/serial_can.py
+++ b/can/interfaces/serial/serial_can.py
@@ -7,6 +7,7 @@
See the interface documentation for the format being used.
"""
+import io
import logging
import struct
from typing import Any, List, Tuple, Optional
@@ -212,10 +213,14 @@ def _recv_internal(
raise CanOperationError("could not read from serial") from error
def fileno(self) -> int:
- if hasattr(self._ser, "fileno"):
+ try:
return self._ser.fileno()
- # Return an invalid file descriptor on Windows
- return -1
+ except io.UnsupportedOperation:
+ raise NotImplementedError(
+ "fileno is not implemented using current CAN bus on this platform"
+ )
+ except Exception as exception:
+ raise CanOperationError("Cannot fetch fileno") from exception
@staticmethod
def _detect_available_configs() -> List[AutoDetectedConfig]:
diff --git a/test/serial_test.py b/test/serial_test.py
index c9c035634..aa6c71994 100644
--- a/test/serial_test.py
+++ b/test/serial_test.py
@@ -136,6 +136,24 @@ def test_rx_tx_min_timestamp_error(self):
msg = can.Message(timestamp=-1)
self.assertRaises(ValueError, self.bus.send, msg)
+ def test_when_no_fileno(self):
+ """
+ Tests for the fileno method catching the missing pyserial implementeation on the Windows platform
+ """
+ try:
+ fileno = self.bus.fileno()
+ except NotImplementedError:
+ pass # allow it to be left non-implemented for Windows platform
+ else:
+ fileno.__gt__ = (
+ lambda self, compare: True
+ ) # Current platform implements fileno, so get the mock to respond to a greater than comparison
+ self.assertIsNotNone(fileno)
+ self.assertFalse(
+ fileno == -1
+ ) # forcing the value to -1 is the old way of managing fileno on Windows but it is not compatible with notifiers
+ self.assertTrue(fileno > 0)
+
class SimpleSerialTest(unittest.TestCase, SimpleSerialTestBase):
def __init__(self, *args, **kwargs):
From d3305225330c2585cf1671532344afe5500352ee Mon Sep 17 00:00:00 2001
From: tamenol <37591107+tamenol@users.noreply.github.com>
Date: Wed, 22 Jun 2022 11:28:12 +0200
Subject: [PATCH 117/475] fix typing in add_listener and remove_listener
(#1335)
* fix typing in add_listener and remove_listener
Use the union to typecheck these functions
* add awaitable to MessageRecipient definition; remove unneeded cast
* remove unused imports
---
can/notifier.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/can/notifier.py b/can/notifier.py
index f7c004c4e..2adae431e 100644
--- a/can/notifier.py
+++ b/can/notifier.py
@@ -6,7 +6,7 @@
import logging
import threading
import time
-from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable
+from typing import Callable, Iterable, List, Optional, Union, Awaitable
from can.bus import BusABC
from can.listener import Listener
@@ -14,7 +14,7 @@
logger = logging.getLogger("can.Notifier")
-MessageRecipient = Union[Listener, Callable[[Message], None]]
+MessageRecipient = Union[Listener, Callable[[Message], Union[Awaitable[None], None]]]
class Notifier:
@@ -140,7 +140,7 @@ def _on_message_available(self, bus: BusABC) -> None:
def _on_message_received(self, msg: Message) -> None:
for callback in self.listeners:
- res = cast(Union[None, Optional[Awaitable[Any]]], callback(msg))
+ res = callback(msg)
if res is not None and self._loop is not None and asyncio.iscoroutine(res):
# Schedule coroutine
self._loop.create_task(res)
@@ -166,7 +166,7 @@ def _on_error(self, exc: Exception) -> bool:
return was_handled
- def add_listener(self, listener: Listener) -> None:
+ def add_listener(self, listener: MessageRecipient) -> None:
"""Add new Listener to the notification list.
If it is already present, it will be called two times
each time a message arrives.
@@ -175,7 +175,7 @@ def add_listener(self, listener: Listener) -> None:
"""
self.listeners.append(listener)
- def remove_listener(self, listener: Listener) -> None:
+ def remove_listener(self, listener: MessageRecipient) -> None:
"""Remove a listener from the notification list. This method
throws an exception if the given listener is not part of the
stored listeners.
From 5bca2d71a7a41692cb1899cd7daa2ecc3710e859 Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Tue, 28 Jun 2022 14:52:47 -0400
Subject: [PATCH 118/475] Allow ICSApiError to be pickled and un-pickled
(#1341)
---
can/interfaces/ics_neovi/neovi_bus.py | 9 +++++++++
test/test_neovi.py | 25 +++++++++++++++++++++++++
2 files changed, 34 insertions(+)
create mode 100644 test/test_neovi.py
diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py
index 95a5bc4c5..5366f8155 100644
--- a/can/interfaces/ics_neovi/neovi_bus.py
+++ b/can/interfaces/ics_neovi/neovi_bus.py
@@ -95,6 +95,15 @@ def __init__(
self.severity = severity
self.restart_needed = restart_needed == 1
+ def __reduce__(self):
+ return type(self), (
+ self.error_code,
+ self.description_short,
+ self.description_long,
+ self.severity,
+ self.restart_needed,
+ )
+
@property
def error_number(self) -> int:
"""Deprecated. Renamed to :attr:`can.CanError.error_code`."""
diff --git a/test/test_neovi.py b/test/test_neovi.py
new file mode 100644
index 000000000..181f92377
--- /dev/null
+++ b/test/test_neovi.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+"""
+"""
+import pickle
+import unittest
+from can.interfaces.ics_neovi import ICSApiError
+
+
+class ICSApiErrorTest(unittest.TestCase):
+ def test_error_pickling(self):
+ iae = ICSApiError(
+ 0xF00,
+ "description_short",
+ "description_long",
+ severity=ICSApiError.ICS_SPY_ERR_CRITICAL,
+ restart_needed=1,
+ )
+ pickled_iae = pickle.dumps(iae)
+ un_pickled_iae = pickle.loads(pickled_iae)
+ assert iae.__dict__ == un_pickled_iae.__dict__
+
+
+if __name__ == "__main__":
+ unittest.main()
From 7b1893d4c7d5654426dcfec4ebcd28ecb5951a85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?uml=C3=A4ute?=
Date: Sun, 10 Jul 2022 16:05:18 +0200
Subject: [PATCH 119/475] Sort interface names, to make documentation
reproducible (#1342)
---
can/logger.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/logger.py b/can/logger.py
index 3594427e6..5aff5e4e1 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -43,7 +43,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
dest="interface",
help="""Specify the backend CAN interface to use. If left blank,
fall back to reading from configuration files.""",
- choices=can.VALID_INTERFACES,
+ choices=sorted(can.VALID_INTERFACES),
)
parser.add_argument(
From f59fe4f8a1f27f202d1a4419b3153dccdfeca68e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?uml=C3=A4ute?=
Date: Mon, 11 Jul 2022 09:55:15 +0200
Subject: [PATCH 120/475] Exclude repository-configuration from git-archive
(#1343)
---
.gitattributes | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 .gitattributes
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..a661f6235
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+.git* export-ignore
+.*.yml export-ignore
From 3ae087963295cd08146cd6b377bcc1f401276018 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 13 Jul 2022 12:34:03 +0200
Subject: [PATCH 121/475] Add py.typed file and distribute it upon installation
(#1344)
---
can/py.typed | 0
setup.py | 1 +
2 files changed, 1 insertion(+)
create mode 100644 can/py.typed
diff --git a/can/py.typed b/can/py.typed
new file mode 100644
index 000000000..e69de29bb
diff --git a/setup.py b/setup.py
index 9dd6153c7..c9defecab 100644
--- a/setup.py
+++ b/setup.py
@@ -78,6 +78,7 @@
"": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"],
"doc": ["*.*"],
"examples": ["*.py"],
+ "can": ["py.typed"],
},
# Installation
# see https://www.python.org/dev/peps/pep-0345/#version-specifiers
From b9d9d01c1c87bc2a4d08b709a0763cdc850f8c35 Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Sun, 24 Jul 2022 19:16:58 +0200
Subject: [PATCH 122/475] Add device_id parameter to PcanBus constructor
(#1346)
* Add device_id parameter to PcanBus constructor
The new device_id parameter can be used to select a PCAN
channel based on the freely programmable device ID of a
PCAN USB device. This change allows for a more deterministic
channel selection since the device ID does not change
between restarts.
* Change wording of PCAN _find_channel_by_dev_id docstring
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Apply black code formatting
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/interfaces/pcan/pcan.py | 41 ++++++++++++++++++++++++++++++++++++-
test/test_pcan.py | 19 +++++++++++++++++
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index b8a0ebee5..2fea74e77 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -103,6 +103,7 @@ class PcanBus(BusABC):
def __init__(
self,
channel="PCAN_USBBUS1",
+ device_id=None,
state=BusState.ACTIVE,
bitrate=500000,
*args,
@@ -119,6 +120,14 @@ def __init__(
Alternatively the value can be an int with the numerical value.
Default is 'PCAN_USBBUS1'
+ :param int device_id:
+ Select the PCAN interface based on its ID. The device ID is a 8/32bit
+ value that can be configured for each PCAN device. If you set the
+ device_id parameter, it takes precedence over the channel parameter.
+ The constructor searches all connected interfaces and initializes the
+ first one that matches the parameter value. If no device is found,
+ an exception is raised.
+
:param can.bus.BusState state:
BusState of the channel.
Default is ACTIVE
@@ -198,6 +207,15 @@ def __init__(
Ignored if not using CAN-FD.
"""
+ self.m_objPCANBasic = PCANBasic()
+
+ if device_id is not None:
+ channel = self._find_channel_by_dev_id(device_id)
+
+ if channel is None:
+ err_msg = "Cannot find a channel with ID {:08x}".format(device_id)
+ raise ValueError(err_msg)
+
self.channel_info = str(channel)
self.fd = kwargs.get("fd", False)
pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K)
@@ -209,7 +227,6 @@ def __init__(
if not isinstance(channel, int):
channel = PCAN_CHANNEL_NAMES[channel]
- self.m_objPCANBasic = PCANBasic()
self.m_PcanHandle = channel
self.check_api_version()
@@ -269,6 +286,28 @@ def __init__(
super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs)
+ def _find_channel_by_dev_id(self, device_id):
+ """
+ Iterate over all possible channels to find a channel that matches the device
+ ID. This method is somewhat brute force, but the Basic API only offers a
+ suitable API call since V4.4.0.
+
+ :param device_id: The device_id for which to search for
+ :return: The name of a PCAN channel that matches the device ID, or None if
+ no channel can be found.
+ """
+ for ch_name, ch_handle in PCAN_CHANNEL_NAMES.items():
+ err, cur_dev_id = self.m_objPCANBasic.GetValue(
+ ch_handle, PCAN_DEVICE_NUMBER
+ )
+ if err != PCAN_ERROR_OK:
+ continue
+
+ if cur_dev_id == device_id:
+ return ch_name
+
+ return None
+
def _get_formatted_error(self, error):
"""
Gets the text using the GetErrorText API function.
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 6fc21184a..eba42d7e1 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -344,6 +344,25 @@ def test_status_string(self, name, status, expected_result) -> None:
self.assertEqual(self.bus.status_string(), expected_result)
self.mock_pcan.GetStatus.assert_called()
+ @parameterized.expand([(0x0, "error"), (0x42, "PCAN_USBBUS8")])
+ def test_constructor_with_device_id(self, dev_id, expected_result):
+ def get_value_side_effect(handle, param):
+ if param == PCAN_API_VERSION:
+ return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
+
+ if handle in (PCAN_USBBUS8, PCAN_USBBUS14):
+ return 0, 0x42
+ else:
+ return PCAN_ERROR_ILLHW, 0x0
+
+ self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect)
+
+ if expected_result == "error":
+ self.assertRaises(ValueError, can.Bus, bustype="pcan", device_id=dev_id)
+ else:
+ self.bus = can.Bus(bustype="pcan", device_id=dev_id)
+ self.assertEqual(expected_result, self.bus.channel_info)
+
if __name__ == "__main__":
unittest.main()
From 788dd048264ca4f82a2c8f994b6dad19655365de Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 6 Aug 2022 23:26:40 +0200
Subject: [PATCH 123/475] Detect types in _parse_additonal_config (#1328)
* detect types in _parse_additonal_config
* check argument format
* fix typo
* recognize extra_args
* fix typo again
---
can/logger.py | 37 ++++++++++++++++++++++++++++--------
can/player.py | 7 ++++---
can/viewer.py | 4 ++--
test/test_logger.py | 46 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 81 insertions(+), 13 deletions(-)
diff --git a/can/logger.py b/can/logger.py
index 5aff5e4e1..209c1381c 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -13,12 +13,12 @@
Dynamic Controls 2010
"""
-
+import re
import sys
import argparse
from datetime import datetime
import errno
-from typing import Any, Dict, List, Union
+from typing import Any, Dict, List, Union, Sequence, Tuple
import can
from . import Bus, BusState, Logger, SizedRotatingLogger
@@ -134,11 +134,32 @@ def _parse_filters(parsed_args: Any) -> CanFilters:
return can_filters
-def _parse_additonal_config(unknown_args):
- return dict(
- (arg.split("=", 1)[0].lstrip("--").replace("-", "_"), arg.split("=", 1)[1])
- for arg in unknown_args
- )
+def _parse_additional_config(
+ unknown_args: Sequence[str],
+) -> Dict[str, Union[str, int, float, bool]]:
+ for arg in unknown_args:
+ if not re.match(r"^--[a-zA-Z\-]*?=\S*?$", arg):
+ raise ValueError(f"Parsing argument {arg} failed")
+
+ def _split_arg(_arg: str) -> Tuple[str, str]:
+ left, right = _arg.split("=", 1)
+ return left.lstrip("--").replace("-", "_"), right
+
+ args: Dict[str, Union[str, int, float, bool]] = {}
+ for key, string_val in map(_split_arg, unknown_args):
+ if re.match(r"^[-+]?\d+$", string_val):
+ # value is integer
+ args[key] = int(string_val)
+ elif re.match(r"^[-+]?\d*\.\d+$", string_val):
+ # value is float
+ args[key] = float(string_val)
+ elif re.match(r"^(?:True|False)$", string_val):
+ # value is bool
+ args[key] = string_val == "True"
+ else:
+ # value is string
+ args[key] = string_val
+ return args
def main() -> None:
@@ -202,7 +223,7 @@ def main() -> None:
raise SystemExit(errno.EINVAL)
results, unknown_args = parser.parse_known_args()
- additional_config = _parse_additonal_config(unknown_args)
+ additional_config = _parse_additional_config(unknown_args)
bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config)
if results.active:
diff --git a/can/player.py b/can/player.py
index 632cc331b..72faa892a 100644
--- a/can/player.py
+++ b/can/player.py
@@ -13,7 +13,7 @@
from can import LogReader, Message, MessageSync
-from .logger import _create_base_argument_parser, _create_bus
+from .logger import _create_base_argument_parser, _create_bus, _parse_additional_config
def main() -> None:
@@ -78,13 +78,14 @@ def main() -> None:
parser.print_help(sys.stderr)
raise SystemExit(errno.EINVAL)
- results = parser.parse_args()
+ results, unknown_args = parser.parse_known_args()
+ additional_config = _parse_additional_config(unknown_args)
verbosity = results.verbosity
error_frames = results.error_frames
- with _create_bus(results) as bus:
+ with _create_bus(results, **additional_config) as bus:
with LogReader(results.infile) as reader:
in_sync = MessageSync(
diff --git a/can/viewer.py b/can/viewer.py
index a84c865f5..7be04949d 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -35,7 +35,7 @@
_parse_filters,
_append_filter_argument,
_create_base_argument_parser,
- _parse_additonal_config,
+ _parse_additional_config,
)
@@ -540,7 +540,7 @@ def parse_args(args):
else:
data_structs[key] = struct.Struct(fmt)
- additional_config = _parse_additonal_config(unknown_args)
+ additional_config = _parse_additional_config(unknown_args)
return parsed_args, can_filters, data_structs, additional_config
diff --git a/test/test_logger.py b/test/test_logger.py
index b694f06bb..bb0015a89 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -10,6 +10,9 @@
import gzip
import os
import sys
+
+import pytest
+
import can
import can.logger
@@ -105,6 +108,49 @@ def test_log_virtual_sizedlogger(self):
self.assertSuccessfullCleanup()
self.mock_logger_sized.assert_called_once()
+ def test_parse_additional_config(self):
+ unknown_args = [
+ "--app-name=CANalyzer",
+ "--serial=5555",
+ "--receive-own-messages=True",
+ "--false-boolean=False",
+ "--offset=1.5",
+ ]
+ parsed_args = can.logger._parse_additional_config(unknown_args)
+
+ assert "app_name" in parsed_args
+ assert parsed_args["app_name"] == "CANalyzer"
+
+ assert "serial" in parsed_args
+ assert parsed_args["serial"] == 5555
+
+ assert "receive_own_messages" in parsed_args
+ assert (
+ isinstance(parsed_args["receive_own_messages"], bool)
+ and parsed_args["receive_own_messages"] is True
+ )
+
+ assert "false_boolean" in parsed_args
+ assert (
+ isinstance(parsed_args["false_boolean"], bool)
+ and parsed_args["false_boolean"] is False
+ )
+
+ assert "offset" in parsed_args
+ assert parsed_args["offset"] == 1.5
+
+ with pytest.raises(ValueError):
+ can.logger._parse_additional_config(["--wrong-format"])
+
+ with pytest.raises(ValueError):
+ can.logger._parse_additional_config(["-wrongformat=value"])
+
+ with pytest.raises(ValueError):
+ can.logger._parse_additional_config(["--wrongformat=value1 value2"])
+
+ with pytest.raises(ValueError):
+ can.logger._parse_additional_config(["wrongformat="])
+
class TestLoggerCompressedFile(unittest.TestCase):
def setUp(self) -> None:
From 616b00e82b4e9f148ddf3799d9edcad525a8deb6 Mon Sep 17 00:00:00 2001
From: Andrew
Date: Sat, 6 Aug 2022 14:32:05 -0700
Subject: [PATCH 124/475] Finds USB2CAN Serial Number by USB Name (#1129)
* Finds usb2can serial numbers by name
* Cleaned up variable names
---
can/interfaces/usb2can/serial_selector.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py
index fcc951262..c6b9053d6 100644
--- a/can/interfaces/usb2can/serial_selector.py
+++ b/can/interfaces/usb2can/serial_selector.py
@@ -40,7 +40,7 @@ def WMIDateStringToDate(dtmDate) -> str:
return strDateTime
-def find_serial_devices(serial_matcher: str = "ED") -> List[str]:
+def find_serial_devices(serial_matcher: str = "") -> List[str]:
"""
Finds a list of USB devices where the serial number (partially) matches the given string.
@@ -49,6 +49,9 @@ def find_serial_devices(serial_matcher: str = "ED") -> List[str]:
"""
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2")
- items = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice")
- ids = (item.Dependent.strip('"')[-8:] for item in items)
- return [e for e in ids if e.startswith(serial_matcher)]
+ query = "SELECT * FROM CIM_LogicalDevice where Name LIKE '%USB2CAN%'"
+ devices = objSWbemServices.ExecQuery(query)
+ serial_numbers = [device.DeviceID.split("\\")[-1] for device in devices]
+ if serial_matcher:
+ return [sn for sn in serial_numbers if serial_matcher in sn]
+ return serial_numbers
From 73663b6bdd98f7d7aed1cbfb75c9a919d22217a4 Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Mon, 8 Aug 2022 01:52:30 -0500
Subject: [PATCH 125/475] Raise appropriate error message when append is not
possible (#1361)
* Raise appropriate error messages when SqliteWriter is requested to roll
- Handle "append" by adding **options to SqlWriter
- Check logger instance for SqliteWriter in rotating logger setup
and raise appropriate error.
* Change **options to **kwargs in can/io/sqlite.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Better error handling for Sqlite rollover and append option
* Add type to **kwargs in asc
* Raise type error rather than exception so tests pass
* Changes to kwargs lookup and rolling log unavailability
* Updates based on black and mypy feedback
* Reformat exception by black
* Handle multiple suffixes in exception
* Replace ValueError with TypeError
* Update append test to handle ValueError
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/asc.py | 6 ++++++
can/io/logger.py | 5 +++--
can/io/sqlite.py | 11 +++++++++--
test/logformats_test.py | 2 +-
4 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/can/io/asc.py b/can/io/asc.py
index 3a320f007..2826a22a6 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -352,6 +352,7 @@ def __init__(
self,
file: Union[StringPathLike, TextIO],
channel: int = 1,
+ **kwargs: Any,
) -> None:
"""
:param file: a path-like object or as file-like object to write to
@@ -360,6 +361,11 @@ def __init__(
:param channel: a default channel to use when the message does not
have a channel set
"""
+ if kwargs.get("append", False):
+ raise ValueError(
+ f"{self.__class__.__name__} is currently not equipped to "
+ f"append messages to an existing file."
+ )
super().__init__(file, mode="w")
self.channel = channel
diff --git a/can/io/logger.py b/can/io/logger.py
index 37ba1e736..5e46f6dc2 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -14,6 +14,7 @@
from typing_extensions import Literal
from pkg_resources import iter_entry_points
+import can.io
from ..message import Message
from ..listener import Listener
from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
@@ -227,8 +228,8 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
return cast(FileIOMessageWriter, logger)
else:
raise Exception(
- "The Logger corresponding to the arguments is not a FileIOMessageWriter or "
- "can.Printer"
+ f"The log format \"{''.join(pathlib.Path(filename).suffixes[-2:])}"
+ f'" is not supported by {self.__class__.__name__}'
)
def stop(self) -> None:
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index 5f05764d5..98e870a84 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -8,7 +8,7 @@
import threading
import logging
import sqlite3
-from typing import Generator
+from typing import Generator, Any
from can.listener import BufferedReader
from can.message import Message
@@ -128,7 +128,9 @@ class SqliteWriter(MessageWriter, BufferedReader):
MAX_BUFFER_SIZE_BEFORE_WRITES = 500
"""Maximum number of messages to buffer before writing to the database"""
- def __init__(self, file: StringPathLike, table_name: str = "messages") -> None:
+ def __init__(
+ self, file: StringPathLike, table_name: str = "messages", **kwargs: Any
+ ) -> None:
"""
:param file: a `str` or path like object that points
to the database file to use
@@ -137,6 +139,11 @@ def __init__(self, file: StringPathLike, table_name: str = "messages") -> None:
.. warning:: In contrary to all other readers/writers the Sqlite handlers
do not accept file-like objects as the `file` parameter.
"""
+ if kwargs.get("append", False):
+ raise ValueError(
+ f"The append argument should not be used in "
+ f"conjunction with the {self.__class__.__name__}."
+ )
super().__init__(file=None)
self.table_name = table_name
self._db_filename = file
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 49e9c563e..6a0eafac1 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -311,7 +311,7 @@ def test_append_mode(self):
# use append mode for second half
try:
writer = self.writer_constructor(self.test_file_name, append=True)
- except TypeError as e:
+ except ValueError as e:
# maybe "append" is not a formal parameter (this is the case for SqliteWriter)
try:
writer = self.writer_constructor(self.test_file_name)
From c1736ccfbc49e27178cded7e4c21bf5ac4e11e1e Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 10 Aug 2022 08:34:25 +0200
Subject: [PATCH 126/475] Add file_size() function to FileIOMessageWriter
(#1367)
* Add file_size() function to FileIOMessageWriter
* Modify help statement for -s to indicate blf behaves differently
* Open max_container_size in blf writer to API via kwargs
Co-authored-by: j-c-cook
---
can/io/blf.py | 11 ++++++++++-
can/io/generic.py | 4 ++++
can/io/logger.py | 2 +-
can/logger.py | 5 +++--
4 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/can/io/blf.py b/can/io/blf.py
index efeba9488..911177d20 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -17,7 +17,7 @@
import datetime
import time
import logging
-from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast
+from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast, Any
from ..message import Message
from ..util import len2dlc, dlc2len, channel2int
@@ -370,6 +370,8 @@ def __init__(
append: bool = False,
channel: int = 1,
compression_level: int = -1,
+ *args: Any,
+ **kwargs: Any
) -> None:
"""
:param file: a path-like object or as file-like object to write to
@@ -400,6 +402,9 @@ def __init__(
self.compression_level = compression_level
self._buffer: List[bytes] = []
self._buffer_size = 0
+ # If max container size is located in kwargs, then update the instance
+ if kwargs.get("max_container_size", False):
+ self.max_container_size = kwargs["max_container_size"]
if append:
# Parse file header
data = self.file.read(FILE_HEADER_STRUCT.size)
@@ -566,6 +571,10 @@ def _flush(self):
self.uncompressed_size += LOG_CONTAINER_STRUCT.size
self.uncompressed_size += len(uncompressed_data)
+ def file_size(self) -> int:
+ """Return an estimate of the current file size in bytes."""
+ return self.file.tell() + self._buffer_size
+
def stop(self):
"""Stops logging and closes the file."""
self._flush()
diff --git a/can/io/generic.py b/can/io/generic.py
index b45e4dd1f..acdf367d6 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -89,6 +89,10 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> N
super().__init__(file, mode)
+ def file_size(self) -> int:
+ """Return an estimate of the current file size in bytes."""
+ return self.file.tell()
+
# pylint: disable=too-few-public-methods
class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta):
diff --git a/can/io/logger.py b/can/io/logger.py
index 5e46f6dc2..071eeb300 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -326,7 +326,7 @@ def should_rollover(self, msg: Message) -> bool:
if self.max_bytes <= 0:
return False
- if self.writer.file.tell() >= self.max_bytes:
+ if self.writer.file_size() >= self.max_bytes:
return True
return False
diff --git a/can/logger.py b/can/logger.py
index 209c1381c..dbf78e408 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -191,8 +191,9 @@ def main() -> None:
"--file_size",
dest="file_size",
type=int,
- help="Maximum file size in bytes. Rotate log file when size threshold "
- "is reached.",
+ help="Maximum file size in bytes (or for the case of blf, maximum "
+ "buffer size before compression and flush to file). Rotate log "
+ "file when size threshold is reached.",
default=None,
)
From 88fc8e5bcf56ab2f9f2041242733ad90bb7101b4 Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Wed, 10 Aug 2022 11:02:34 +0200
Subject: [PATCH 127/475] Fix race condition in back2back_test for UDP
multicast bus (#1349)
* Add debug prints to back2back_test to analyze race condition
Might fail if pytest does not print stdout
* Update IPv6 back2back test to use interface-local multicast address
Under unknown conditions the IPv6 multicast frames sent during the
test are reflected back to the sender twice. This causes the test to
fail. With this commit, the multicast frames sent during the test are
constrained to the local interface to potentially avoid a double
reception.
* Revert "Add debug prints to back2back_test to analyze race condition"
This reverts commit a830c0900dee465d3d7ffa9697f49791dde722da.
---
test/back2back_test.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/test/back2back_test.py b/test/back2back_test.py
index b5ae4e27c..ab4d57dc1 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -307,11 +307,12 @@ def test_unique_message_instances(self):
"only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv6(Back2BackTestCase):
+ HOST_LOCAL_MCAST_GROUP_IPv6 = "ff11:7079:7468:6f6e:6465:6d6f:6d63:6173"
INTERFACE_1 = "udp_multicast"
- CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv6
+ CHANNEL_1 = HOST_LOCAL_MCAST_GROUP_IPv6
INTERFACE_2 = "udp_multicast"
- CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv6
+ CHANNEL_2 = HOST_LOCAL_MCAST_GROUP_IPv6
def test_unique_message_instances(self):
with self.assertRaises(NotImplementedError):
From 9e795de62166c37c19770fb4aaf1a5ac966dff9c Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 11 Aug 2022 23:26:57 +0200
Subject: [PATCH 128/475] refactor for mypy friendly version checks (#1371)
---
can/interfaces/__init__.py | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 90a05d7bc..755e8675c 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -2,8 +2,11 @@
Interfaces contain low level implementations that interact with CAN hardware.
"""
+import sys
+from typing import Dict, Tuple
+
# interface_name => (module, classname)
-BACKENDS = {
+BACKENDS: Dict[str, Tuple[str, ...]] = {
"kvaser": ("can.interfaces.kvaser", "KvaserBus"),
"socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
"serial": ("can.interfaces.serial.serial_can", "SerialBus"),
@@ -29,27 +32,21 @@
"socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
}
-try:
+if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
- try:
- entries = entry_points(group="can.interface")
- except TypeError:
- # Fallback for Python <3.10
- # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note"
- entries = entry_points().get("can.interface", [])
-
+ entries = entry_points().get("can.interface", ())
BACKENDS.update(
{interface.name: tuple(interface.value.split(":")) for interface in entries}
)
-except ImportError:
+else:
from pkg_resources import iter_entry_points
- entry = iter_entry_points("can.interface")
+ entries = iter_entry_points("can.interface")
BACKENDS.update(
{
interface.name: (interface.module_name, interface.attrs[0])
- for interface in entry
+ for interface in entries
}
)
From 3a7c80eb24d253b092b038f45be9d36942c2ab2d Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Thu, 11 Aug 2022 17:00:38 -0500
Subject: [PATCH 129/475] Write 3 ms digits to ascii to resolve CANoe bug
(#1362)
* Write 3 ms to ascii for CANoe and fix append error
The ascii format currently is not setup to append. I recenly added
in **options to the logger script. **options are only required for
the rolling logger.
* Black and mypy fixes
* Revert "Write 3 ms to ascii for CANoe and fix append error"
This reverts commit 57af021eba6218186a30af684e0b96fbbcac209d.
---
can/io/asc.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/can/io/asc.py b/can/io/asc.py
index 2826a22a6..ba2214c71 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -372,6 +372,10 @@ def __init__(
# write start of file header
now = datetime.now().strftime(self.FORMAT_START_OF_FILE_DATE)
+ # Note: CANoe requires that the microsecond field only have 3 digits
+ idx = now.index(".") # Find the index in the string of the decimal
+ # Keep decimal and first three ms digits (4), remove remaining digits
+ now = now.replace(now[idx + 4 : now[idx:].index(" ") + idx], "")
self.file.write(f"date {now}\n")
self.file.write("base hex timestamps absolute\n")
self.file.write("internal events logged\n")
From c4f0789afb756abaa57efe83d222b28b9343d8ab Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 12 Aug 2022 19:03:35 +0200
Subject: [PATCH 130/475] Pass CLI extra_args to Logger initialisation (#1366)
* pass CLI extra_args to Logger
* pass extra_args to player
---
can/io/asc.py | 3 +++
can/io/blf.py | 9 +++++++--
can/io/canutils.py | 13 ++++++++++---
can/io/csv.py | 15 ++++++++++++---
can/io/generic.py | 15 +++++++++++++--
can/io/logger.py | 2 +-
can/io/printer.py | 8 ++++++--
can/io/sqlite.py | 14 ++++++++++++--
can/logger.py | 25 +++++++++++++++++--------
can/player.py | 4 ++--
can/viewer.py | 4 +++-
11 files changed, 86 insertions(+), 26 deletions(-)
diff --git a/can/io/asc.py b/can/io/asc.py
index ba2214c71..7cefa5b76 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -39,6 +39,8 @@ def __init__(
file: Union[StringPathLike, TextIO],
base: str = "hex",
relative_timestamp: bool = True,
+ *args: Any,
+ **kwargs: Any,
) -> None:
"""
:param file: a path-like object or as file-like object to read from
@@ -352,6 +354,7 @@ def __init__(
self,
file: Union[StringPathLike, TextIO],
channel: int = 1,
+ *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/blf.py b/can/io/blf.py
index 911177d20..93fa54ca2 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -143,7 +143,12 @@ class BLFReader(MessageReader):
file: BinaryIO
- def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
+ def __init__(
+ self,
+ file: Union[StringPathLike, BinaryIO],
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in binary
@@ -371,7 +376,7 @@ def __init__(
channel: int = 1,
compression_level: int = -1,
*args: Any,
- **kwargs: Any
+ **kwargs: Any,
) -> None:
"""
:param file: a path-like object or as file-like object to write to
diff --git a/can/io/canutils.py b/can/io/canutils.py
index 0cca82eb8..f63df3f6b 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -5,11 +5,11 @@
"""
import logging
-from typing import Generator, TextIO, Union
+from typing import Generator, TextIO, Union, Any
from can.message import Message
from .generic import FileIOMessageWriter, MessageReader
-from ..typechecking import AcceptedIOType, StringPathLike
+from ..typechecking import StringPathLike
log = logging.getLogger("can.io.canutils")
@@ -34,7 +34,12 @@ class CanutilsLogReader(MessageReader):
file: TextIO
- def __init__(self, file: Union[StringPathLike, TextIO]) -> None:
+ def __init__(
+ self,
+ file: Union[StringPathLike, TextIO],
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in text
@@ -132,6 +137,8 @@ def __init__(
file: Union[StringPathLike, TextIO],
channel: str = "vcan0",
append: bool = False,
+ *args: Any,
+ **kwargs: Any,
):
"""
:param file: a path-like object or as file-like object to write to
diff --git a/can/io/csv.py b/can/io/csv.py
index 0161b4f55..2e2f46699 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -10,7 +10,7 @@
"""
from base64 import b64encode, b64decode
-from typing import TextIO, Generator, Union
+from typing import TextIO, Generator, Union, Any
from can.message import Message
from .generic import FileIOMessageWriter, MessageReader
@@ -28,7 +28,12 @@ class CSVReader(MessageReader):
file: TextIO
- def __init__(self, file: Union[StringPathLike, TextIO]) -> None:
+ def __init__(
+ self,
+ file: Union[StringPathLike, TextIO],
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in text
@@ -87,7 +92,11 @@ class CSVWriter(FileIOMessageWriter):
file: TextIO
def __init__(
- self, file: Union[StringPathLike, TextIO], append: bool = False
+ self,
+ file: Union[StringPathLike, TextIO],
+ append: bool = False,
+ *args: Any,
+ **kwargs: Any,
) -> None:
"""
:param file: a path-like object or a file-like object to write to.
diff --git a/can/io/generic.py b/can/io/generic.py
index acdf367d6..d5c7a2057 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -7,6 +7,7 @@
Iterable,
Type,
ContextManager,
+ Any,
)
from typing_extensions import Literal
from types import TracebackType
@@ -28,7 +29,11 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta):
file: Optional[can.typechecking.FileLike]
def __init__(
- self, file: Optional[can.typechecking.AcceptedIOType], mode: str = "rt"
+ self,
+ file: Optional[can.typechecking.AcceptedIOType],
+ mode: str = "rt",
+ *args: Any,
+ **kwargs: Any
) -> None:
"""
:param file: a path-like object to open a file, a file-like object
@@ -82,7 +87,13 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
file: can.typechecking.FileLike
- def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> None:
+ def __init__(
+ self,
+ file: can.typechecking.AcceptedIOType,
+ mode: str = "wt",
+ *args: Any,
+ **kwargs: Any
+ ) -> None:
# Not possible with the type signature, but be verbose for user-friendliness
if file is None:
raise ValueError("The given file cannot be None")
diff --git a/can/io/logger.py b/can/io/logger.py
index 071eeb300..b50825d3f 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -303,8 +303,8 @@ class SizedRotatingLogger(BaseRotatingLogger):
def __init__(
self,
base_filename: StringPathLike,
- *args: Any,
max_bytes: int = 0,
+ *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/printer.py b/can/io/printer.py
index cafab3815..61871e8ad 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -4,7 +4,7 @@
import logging
-from typing import Optional, TextIO, Union
+from typing import Optional, TextIO, Union, Any
from ..message import Message
from .generic import MessageWriter
@@ -26,7 +26,11 @@ class Printer(MessageWriter):
file: Optional[TextIO]
def __init__(
- self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False
+ self,
+ file: Optional[Union[StringPathLike, TextIO]] = None,
+ append: bool = False,
+ *args: Any,
+ **kwargs: Any
) -> None:
"""
:param file: An optional path-like object or a file-like object to "print"
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index 98e870a84..b9cbf9f93 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -32,7 +32,13 @@ class SqliteReader(MessageReader):
.. note:: The database schema is given in the documentation of the loggers.
"""
- def __init__(self, file: StringPathLike, table_name: str = "messages") -> None:
+ def __init__(
+ self,
+ file: StringPathLike,
+ table_name: str = "messages",
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
"""
:param file: a `str` path like object that points
to the database file to use
@@ -129,7 +135,11 @@ class SqliteWriter(MessageWriter, BufferedReader):
"""Maximum number of messages to buffer before writing to the database"""
def __init__(
- self, file: StringPathLike, table_name: str = "messages", **kwargs: Any
+ self,
+ file: StringPathLike,
+ table_name: str = "messages",
+ *args: Any,
+ **kwargs: Any,
) -> None:
"""
:param file: a `str` or path like object that points
diff --git a/can/logger.py b/can/logger.py
index dbf78e408..0b73dd785 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -21,6 +21,8 @@
from typing import Any, Dict, List, Union, Sequence, Tuple
import can
+from can.io import BaseRotatingLogger
+from can.io.generic import MessageWriter
from . import Bus, BusState, Logger, SizedRotatingLogger
from .typechecking import CanFilter, CanFilters
@@ -61,10 +63,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"extra_args",
nargs=argparse.REMAINDER,
- help=r"The remaining arguments will be used for the interface "
- r"initialisation. For example, `-i vector -c 1 --app-name="
- r"MyCanApp` is the equivalent to opening the bus with `Bus("
- r"'vector', channel=1, app_name='MyCanApp')",
+ help="The remaining arguments will be used for the interface and "
+ "logger/player initialisation. "
+ "For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent "
+ "to opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')",
)
@@ -224,7 +226,7 @@ def main() -> None:
raise SystemExit(errno.EINVAL)
results, unknown_args = parser.parse_known_args()
- additional_config = _parse_additional_config(unknown_args)
+ additional_config = _parse_additional_config([*results.extra_args, *unknown_args])
bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config)
if results.active:
@@ -235,13 +237,20 @@ def main() -> None:
print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}")
print(f"Can Logger (Started on {datetime.now()})")
- options = {"append": results.append}
+ logger: Union[MessageWriter, BaseRotatingLogger]
if results.file_size:
logger = SizedRotatingLogger(
- base_filename=results.log_file, max_bytes=results.file_size, **options
+ base_filename=results.log_file,
+ max_bytes=results.file_size,
+ append=results.append,
+ **additional_config,
)
else:
- logger = Logger(filename=results.log_file, **options) # type: ignore
+ logger = Logger(
+ filename=results.log_file,
+ append=results.append,
+ **additional_config,
+ )
try:
while True:
diff --git a/can/player.py b/can/player.py
index 72faa892a..c029981be 100644
--- a/can/player.py
+++ b/can/player.py
@@ -79,14 +79,14 @@ def main() -> None:
raise SystemExit(errno.EINVAL)
results, unknown_args = parser.parse_known_args()
- additional_config = _parse_additional_config(unknown_args)
+ additional_config = _parse_additional_config([*results.extra_args, *unknown_args])
verbosity = results.verbosity
error_frames = results.error_frames
with _create_bus(results, **additional_config) as bus:
- with LogReader(results.infile) as reader:
+ with LogReader(results.infile, **additional_config) as reader:
in_sync = MessageSync(
cast(Iterable[Message], reader),
diff --git a/can/viewer.py b/can/viewer.py
index 7be04949d..6773a0acd 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -540,7 +540,9 @@ def parse_args(args):
else:
data_structs[key] = struct.Struct(fmt)
- additional_config = _parse_additional_config(unknown_args)
+ additional_config = _parse_additional_config(
+ [*parsed_args.extra_args, *unknown_args]
+ )
return parsed_args, can_filters, data_structs, additional_config
From ed124c92f38b454ca2f485ca4c9557eb47b44951 Mon Sep 17 00:00:00 2001
From: Atabey <55498083+1atabey1@users.noreply.github.com>
Date: Fri, 12 Aug 2022 19:26:26 +0200
Subject: [PATCH 131/475] Add Parameter for Enabling PCAN Auto Bus-Off Reset
(#1345)
* parametrise pcan auto reset
* add unit tests, fix build error
* formatting
Co-authored-by: Atabey
---
can/interfaces/pcan/pcan.py | 13 +++++++++++++
test/test_pcan.py | 10 ++++++++++
2 files changed, 23 insertions(+)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 2fea74e77..e5b877762 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -56,6 +56,7 @@
PCAN_CHANNEL_FEATURES,
FEATURE_FD_CAPABLE,
PCAN_DICT_STATUS,
+ PCAN_BUSOFF_AUTORESET,
)
@@ -206,6 +207,10 @@ def __init__(
In the range (1..16).
Ignored if not using CAN-FD.
+ :param bool auto_reset:
+ Enable automatic recovery in bus off scenario.
+ Resetting the driver takes ~500ms during which
+ it will not be responsive.
"""
self.m_objPCANBasic = PCANBasic()
@@ -276,6 +281,14 @@ def __init__(
"Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.10"
)
+ if kwargs.get("auto_reset", False):
+ result = self.m_objPCANBasic.SetValue(
+ self.m_PcanHandle, PCAN_BUSOFF_AUTORESET, PCAN_PARAMETER_ON
+ )
+
+ if result != PCAN_ERROR_OK:
+ raise PcanCanInitializationError(self._get_formatted_error(result))
+
if HAS_EVENTS:
self._recv_event = CreateEvent(None, 0, 0, None)
result = self.m_objPCANBasic.SetValue(
diff --git a/test/test_pcan.py b/test/test_pcan.py
index eba42d7e1..0a680fea0 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -363,6 +363,16 @@ def get_value_side_effect(handle, param):
self.bus = can.Bus(bustype="pcan", device_id=dev_id)
self.assertEqual(expected_result, self.bus.channel_info)
+ def test_bus_creation_auto_reset(self):
+ self.bus = can.Bus(bustype="pcan", auto_reset=True)
+ self.assertIsInstance(self.bus, PcanBus)
+ self.MockPCANBasic.assert_called_once()
+
+ def test_auto_reset_init_fault(self):
+ self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_INITIALIZE)
+ with self.assertRaises(CanInitializationError):
+ self.bus = can.Bus(bustype="pcan", auto_reset=True)
+
if __name__ == "__main__":
unittest.main()
From f6d8fe0432d46ea48027511bd38ae4c141a9a5e4 Mon Sep 17 00:00:00 2001
From: "Fede.Breg" <44495483+Thepowa753@users.noreply.github.com>
Date: Fri, 12 Aug 2022 19:47:54 +0200
Subject: [PATCH 132/475] fix: conversion for port number (#1309)
* fix: conversion for port number
* fix: correct order for valid port range
* refactor: tabs and spaces Sorry
* fix: calling port within if in dict
* fix: forcing int to bypass wrong error
* feat: add util port tests
* fix: tests assertRaises with correct syntax
* refactor: using black
* make mypy happy
Co-authored-by: Federico Bregant
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/util.py | 14 ++++++++++++++
test/test_util.py | 14 ++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/can/util.py b/can/util.py
index a9d08c469..d1ff643de 100644
--- a/can/util.py
+++ b/can/util.py
@@ -211,6 +211,20 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
raise CanInterfaceNotImplementedError(
f'Unknown interface type "{config["interface"]}"'
)
+ if "port" in config:
+ # convert port to integer if necessary
+ if isinstance(config["port"], int):
+ port = config["port"]
+ elif isinstance(config["port"], str):
+ if config["port"].isnumeric():
+ config["port"] = port = int(config["port"])
+ else:
+ raise ValueError("Port config must be a number!")
+ else:
+ raise TypeError("Port config must be string or integer!")
+
+ if not 0 < port < 65535:
+ raise ValueError("Port config must be inside 0-65535 range!")
if "bitrate" in config:
config["bitrate"] = int(config["bitrate"])
diff --git a/test/test_util.py b/test/test_util.py
index e151e3d63..7048d6151 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -53,6 +53,9 @@ def test_with_new_and_alias_present(self):
class TestBusConfig(unittest.TestCase):
base_config = dict(interface="socketcan", bitrate=500_000)
+ port_alpha_config = dict(interface="socketcan", bitrate=500_000, port="fail123")
+ port_to_high_config = dict(interface="socketcan", bitrate=500_000, port="999999")
+ port_wrong_type_config = dict(interface="socketcan", bitrate=500_000, port=(1234,))
def test_timing_can_use_int(self):
"""
@@ -64,6 +67,17 @@ def test_timing_can_use_int(self):
_create_bus_config({**self.base_config, **timing_conf})
except TypeError as e:
self.fail(e)
+ self.assertRaises(
+ ValueError, _create_bus_config, {**self.port_alpha_config, **timing_conf}
+ )
+ self.assertRaises(
+ ValueError, _create_bus_config, {**self.port_to_high_config, **timing_conf}
+ )
+ self.assertRaises(
+ TypeError,
+ _create_bus_config,
+ {**self.port_wrong_type_config, **timing_conf},
+ )
class TestChannel2Int(unittest.TestCase):
From ae41b3e145a6b2ca6d0c6834cf5f0a54096472bc Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 12 Aug 2022 19:59:04 +0200
Subject: [PATCH 133/475] replace socket.error with OSError (#1373)
---
can/interfaces/socketcan/socketcan.py | 16 ++++++++--------
can/interfaces/socketcand/socketcand.py | 2 +-
can/interfaces/udp_multicast/bus.py | 2 +-
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index 082bbcf19..c7c038520 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -530,7 +530,7 @@ def capture_message(
channel = addr[0] if isinstance(addr, tuple) else addr
else:
channel = None
- except socket.error as error:
+ except OSError as error:
raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno)
can_id, can_dlc, flags, data = dissect_can_frame(cf)
@@ -656,7 +656,7 @@ def __init__(
self.socket.setsockopt(
SOL_CAN_RAW, CAN_RAW_LOOPBACK, 1 if local_loopback else 0
)
- except socket.error as error:
+ except OSError as error:
log.error("Could not set local loopback flag(%s)", error)
# set the receive_own_messages parameter
@@ -664,21 +664,21 @@ def __init__(
self.socket.setsockopt(
SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0
)
- except socket.error as error:
+ except OSError as error:
log.error("Could not receive own messages (%s)", error)
# enable CAN-FD frames if desired
if fd:
try:
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1)
- except socket.error as error:
+ except OSError as error:
log.error("Could not enable CAN-FD frames (%s)", error)
if not ignore_rx_error_frames:
# enable error frames
try:
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF)
- except socket.error as error:
+ except OSError as error:
log.error("Could not enable error frames (%s)", error)
# enable nanosecond resolution timestamping
@@ -714,7 +714,7 @@ def _recv_internal(
# 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 error:
+ except OSError as error:
# something bad happened (e.g. the interface went down)
raise can.CanOperationError(
f"Failed to receive: {error.strerror}", error.errno
@@ -776,7 +776,7 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int:
sent = self.socket.sendto(data, (channel,))
else:
sent = self.socket.send(data)
- except socket.error as error:
+ except OSError as error:
raise can.CanOperationError(
f"Failed to transmit: {error.strerror}", error.errno
)
@@ -840,7 +840,7 @@ def _get_bcm_socket(self, channel: str) -> socket.socket:
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
try:
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters))
- except socket.error as error:
+ except OSError as error:
# fall back to "software filtering" (= not in kernel)
self._is_filtered = False
log.error(
diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py
index 6b74b59e0..327df9e73 100644
--- a/can/interfaces/socketcand/socketcand.py
+++ b/can/interfaces/socketcand/socketcand.py
@@ -93,7 +93,7 @@ def _recv_internal(self, timeout):
ready_receive_sockets, _, _ = select.select(
[self.__socket], [], [], timeout
)
- except socket.error as exc:
+ except OSError as exc:
# something bad happened (e.g. the interface went down)
log.error(f"Failed to receive: {exc}")
raise can.CanError(f"Failed to receive: {exc}")
diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py
index 6b7e57bd9..7f74c685f 100644
--- a/can/interfaces/udp_multicast/bus.py
+++ b/can/interfaces/udp_multicast/bus.py
@@ -332,7 +332,7 @@ def recv(
# 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:
+ except OSError as exc:
# something bad (not a timeout) happened (e.g. the interface went down)
raise can.CanOperationError(
f"Failed to wait for IP/UDP socket: {exc}"
From 2da28c1a1c87776618a60218b0b97800cf2deb34 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 15 Aug 2022 08:33:15 +0200
Subject: [PATCH 134/475] improve MessageSync timings (#1374)
---
can/io/player.py | 25 ++++++++++++++----------
test/test_message_sync.py | 40 ++++++++++++++++++---------------------
test/test_player.py | 9 ++-------
3 files changed, 35 insertions(+), 39 deletions(-)
diff --git a/can/io/player.py b/can/io/player.py
index 132751f4d..8eb4ba24f 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -5,7 +5,7 @@
"""
import gzip
import pathlib
-from time import time, sleep
+import time
import typing
from pkg_resources import iter_entry_points
@@ -132,8 +132,9 @@ def __init__(
self.skip = skip
def __iter__(self) -> typing.Generator[Message, None, None]:
- playback_start_time = time()
+ t_wakeup = playback_start_time = time.perf_counter()
recorded_start_time = None
+ t_skipped = 0.0
for message in self.raw_messages:
@@ -142,15 +143,19 @@ def __iter__(self) -> typing.Generator[Message, None, None]:
if recorded_start_time is None:
recorded_start_time = message.timestamp
- now = time()
- current_offset = now - playback_start_time
- recorded_offset_from_start = message.timestamp - recorded_start_time
- remaining_gap = max(0.0, recorded_offset_from_start - current_offset)
-
- sleep_period = max(self.gap, min(self.skip, remaining_gap))
+ t_wakeup = playback_start_time + (
+ message.timestamp - t_skipped - recorded_start_time
+ )
else:
- sleep_period = self.gap
+ t_wakeup += self.gap
+
+ sleep_period = t_wakeup - time.perf_counter()
+
+ if self.skip and sleep_period > self.skip:
+ t_skipped += sleep_period - self.skip
+ sleep_period = self.skip
- sleep(sleep_period)
+ if sleep_period > 1e-4:
+ time.sleep(sleep_period)
yield message
diff --git a/test/test_message_sync.py b/test/test_message_sync.py
index 1e2d61b24..8750dd416 100644
--- a/test/test_message_sync.py
+++ b/test/test_message_sync.py
@@ -5,7 +5,7 @@
"""
from copy import copy
-from time import time
+import time
import gc
import unittest
@@ -49,43 +49,40 @@ def teardown_method(self, _):
# we need to reenable the garbage collector again
gc.enable()
- @pytest.mark.timeout(inc(0.2))
def test_general(self):
messages = [
Message(timestamp=50.0),
Message(timestamp=50.0),
Message(timestamp=50.0 + 0.05),
- Message(timestamp=50.0 + 0.05 + 0.08),
+ Message(timestamp=50.0 + 0.13),
Message(timestamp=50.0), # back in time
]
- sync = MessageSync(messages, gap=0.0)
+ sync = MessageSync(messages, gap=0.0, skip=0.0)
- start = time()
+ t_start = time.perf_counter()
collected = []
timings = []
for message in sync:
+ t_now = time.perf_counter()
collected.append(message)
- now = time()
- timings.append(now - start)
- start = now
+ timings.append(t_now - t_start)
self.assertMessagesEqual(messages, collected)
self.assertEqual(len(timings), len(messages), "programming error in test code")
- self.assertTrue(0.0 <= timings[0] < inc(0.005), str(timings[0]))
- self.assertTrue(0.0 <= timings[1] < inc(0.005), str(timings[1]))
- self.assertTrue(0.045 <= timings[2] < inc(0.055), str(timings[2]))
- self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3]))
- self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4]))
+ self.assertTrue(0.0 <= timings[0] < 0.0 + inc(0.02), str(timings[0]))
+ self.assertTrue(0.0 <= timings[1] < 0.0 + inc(0.02), str(timings[1]))
+ self.assertTrue(0.045 <= timings[2] < 0.05 + inc(0.02), str(timings[2]))
+ self.assertTrue(0.125 <= timings[3] < 0.13 + inc(0.02), str(timings[3]))
+ self.assertTrue(0.125 <= timings[4] < 0.13 + inc(0.02), str(timings[4]))
- @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative
def test_skip(self):
messages = copy(TEST_FEWER_MESSAGES)
sync = MessageSync(messages, skip=0.005, gap=0.0)
- before = time()
+ before = time.perf_counter()
collected = list(sync)
- after = time()
+ after = time.perf_counter()
took = after - before
# the handling of the messages itself also takes some time:
@@ -96,9 +93,8 @@ def test_skip(self):
@skip_on_unreliable_platforms
-@pytest.mark.timeout(inc(0.3))
@pytest.mark.parametrize(
- "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)]
+ "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 1.5)]
)
def test_gap(timestamp_1, timestamp_2):
"""This method is alone so it can be parameterized."""
@@ -106,16 +102,16 @@ def test_gap(timestamp_1, timestamp_2):
Message(arbitration_id=0x1, timestamp=timestamp_1),
Message(arbitration_id=0x2, timestamp=timestamp_2),
]
- sync = MessageSync(messages, gap=0.1)
+ sync = MessageSync(messages, timestamps=False, gap=0.1)
gc.disable()
- before = time()
+ before = time.perf_counter()
collected = list(sync)
- after = time()
+ after = time.perf_counter()
gc.enable()
took = after - before
- assert 0.1 <= took < inc(0.3)
+ assert 0.195 <= took < 0.2 + inc(0.02)
assert messages == collected
diff --git a/test/test_player.py b/test/test_player.py
index 0001ae678..c15bf82e2 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -27,7 +27,7 @@ def setUp(self) -> None:
self.mock_virtual_bus.__enter__ = Mock(return_value=self.mock_virtual_bus)
# Patch time sleep object
- patcher_sleep = mock.patch("can.io.player.sleep", spec=True)
+ patcher_sleep = mock.patch("can.io.player.time.sleep", spec=True)
self.MockSleep = patcher_sleep.start()
self.addCleanup(patcher_sleep.stop)
@@ -60,7 +60,6 @@ def test_play_virtual(self):
dlc=8,
data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
)
- self.assertEqual(self.MockSleep.call_count, 2)
if sys.version_info >= (3, 8):
# The args argument was introduced with python 3.8
self.assertTrue(
@@ -78,7 +77,6 @@ def test_play_virtual_verbose(self):
self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue())
self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue())
self.assertEqual(self.mock_virtual_bus.send.call_count, 2)
- self.assertEqual(self.MockSleep.call_count, 2)
self.assertSuccessfulCleanup()
def test_play_virtual_exit(self):
@@ -86,8 +84,7 @@ def test_play_virtual_exit(self):
sys.argv = self.baseargs + [self.logfile]
can.player.main()
- self.assertEqual(self.mock_virtual_bus.send.call_count, 1)
- self.assertEqual(self.MockSleep.call_count, 2)
+ assert self.mock_virtual_bus.send.call_count <= 2
self.assertSuccessfulCleanup()
def test_play_skip_error_frame(self):
@@ -97,7 +94,6 @@ def test_play_skip_error_frame(self):
sys.argv = self.baseargs + ["-v", logfile]
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 9)
- self.assertEqual(self.MockSleep.call_count, 12)
self.assertSuccessfulCleanup()
def test_play_error_frame(self):
@@ -107,7 +103,6 @@ def test_play_error_frame(self):
sys.argv = self.baseargs + ["-v", "--error-frames", logfile]
can.player.main()
self.assertEqual(self.mock_virtual_bus.send.call_count, 12)
- self.assertEqual(self.MockSleep.call_count, 12)
self.assertSuccessfulCleanup()
From 9d9cb16f474f837bd46eabb1350ecf86e8d2c42b Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Sun, 28 Aug 2022 08:37:00 -0500
Subject: [PATCH 135/475] Fix _default_name for compressed files (#1383)
* Fix _default_name for compressed files
* Handle a filepath that contains "." prior to stem
* Reuse local path variable
---
can/io/logger.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index b50825d3f..478651953 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -345,11 +345,11 @@ def _default_name(self) -> StringPathLike:
"""Generate the default rotation filename."""
path = pathlib.Path(self.base_filename)
new_name = (
- path.stem
+ path.stem.split(".")[0]
+ "_"
+ datetime.now().strftime("%Y-%m-%dT%H%M%S")
+ "_"
+ f"#{self.rollover_count:03}"
- + path.suffix
+ + "".join(path.suffixes[-2:])
)
return str(path.parent / new_name)
From ad8b9482ba9fe76f1a4b746f20a9e7a216f6d9f9 Mon Sep 17 00:00:00 2001
From: Maksim Salau
Date: Sat, 3 Sep 2022 19:59:06 +0200
Subject: [PATCH 136/475] socketcan: Make find_available_interfaces() find
slcanX interfaces (#1369)
* socketcan: Make find_available_interfaces() find slcanX interfaces
* doc: socketcan: Fix external link to can-utils on github
* socketcan: Extend name interface pattern to match vxcan\d+
---
can/interfaces/socketcan/utils.py | 2 +-
doc/interfaces/socketcan.rst | 22 ++++++++++++++++++++++
test/open_vcan.sh | 4 ++++
test/test_socketcan_helpers.py | 6 ++++--
4 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py
index b718bb69e..55e7eb392 100644
--- a/can/interfaces/socketcan/utils.py
+++ b/can/interfaces/socketcan/utils.py
@@ -38,7 +38,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes
return struct.pack(can_filter_fmt, *filter_data)
-_PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+")
+_PATTERN_CAN_INTERFACE = re.compile(r"(sl|v|vx)?can\d+")
def find_available_interfaces() -> Iterable[str]:
diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst
index d3a583d75..1e82d8827 100644
--- a/doc/interfaces/socketcan.rst
+++ b/doc/interfaces/socketcan.rst
@@ -57,6 +57,28 @@ existing ``can0`` interface with a bitrate of 1MB:
sudo ip link set can0 up type can bitrate 1000000
+CAN over Serial / SLCAN
+~~~~~~~~~~~~~~~~~~~~~~~
+
+SLCAN adapters can be used directly via :doc:`/interfaces/slcan`, or
+via :doc:`/interfaces/socketcan` with some help from the ``slcand`` utility
+which can be found in the `can-utils `_ package.
+
+To create a socketcan interface for an SLCAN adapter run the following:
+
+.. code-block:: bash
+
+ slcand -f -o -c -s5 /dev/ttyAMA0
+ ip link set up slcan0
+
+Names of the interfaces created by ``slcand`` match the ``slcan\d+`` regex.
+If a custom name is required, it can be specified as the last argument. E.g.:
+
+.. code-block:: bash
+
+ slcand -f -o -c -s5 /dev/ttyAMA0 can0
+ ip link set up can0
+
.. _socketcan-pcan:
PCAN
diff --git a/test/open_vcan.sh b/test/open_vcan.sh
index bd02ad752..b6f4676b7 100755
--- a/test/open_vcan.sh
+++ b/test/open_vcan.sh
@@ -5,3 +5,7 @@
modprobe vcan
ip link add dev vcan0 type vcan
ip link set up vcan0 mtu 72
+ip link add dev vxcan0 type vcan
+ip link set up vxcan0 mtu 72
+ip link add dev slcan0 type vcan
+ip link set up slcan0 mtu 72
diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py
index f3fbe6d26..ad53836f2 100644
--- a/test/test_socketcan_helpers.py
+++ b/test/test_socketcan_helpers.py
@@ -31,10 +31,12 @@ def test_find_available_interfaces(self):
result = list(find_available_interfaces())
self.assertGreaterEqual(len(result), 0)
for entry in result:
- self.assertRegex(entry, r"v?can\d+")
+ self.assertRegex(entry, r"(sl|v|vx)?can\d+")
if TEST_INTERFACE_SOCKETCAN:
- self.assertGreaterEqual(len(result), 1)
+ self.assertGreaterEqual(len(result), 3)
self.assertIn("vcan0", result)
+ self.assertIn("vxcan0", result)
+ self.assertIn("slcan0", result)
if __name__ == "__main__":
From 40f6cce796ef5969764e18e746d0dab8d1adab63 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 3 Sep 2022 20:48:06 +0200
Subject: [PATCH 137/475] extend XL api wrapper (#1387)
---
can/interfaces/vector/xlclass.py | 13 ++++++++++++-
can/interfaces/vector/xldefine.py | 8 +++++++-
can/interfaces/vector/xldriver.py | 15 +++++++++++++++
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py
index c0ef9fa48..6441ad4e5 100644
--- a/can/interfaces/vector/xlclass.py
+++ b/can/interfaces/vector/xlclass.py
@@ -45,6 +45,13 @@ class s_xl_chip_state(ctypes.Structure):
]
+class s_xl_sync_pulse(ctypes.Structure):
+ _fields_ = [
+ ("pulseCode", ctypes.c_ubyte),
+ ("time", XLuint64),
+ ]
+
+
class s_xl_can_ev_chip_state(ctypes.Structure):
_fields_ = [
("busStatus", ctypes.c_ubyte),
@@ -65,7 +72,11 @@ class s_xl_can_ev_sync_pulse(ctypes.Structure):
# BASIC bus message structure
class s_xl_tag_data(ctypes.Union):
- _fields_ = [("msg", s_xl_can_msg), ("chipState", s_xl_chip_state)]
+ _fields_ = [
+ ("msg", s_xl_can_msg),
+ ("chipState", s_xl_chip_state),
+ ("syncPulse", s_xl_sync_pulse),
+ ]
# CAN FD messages
diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py
index 032f08318..5a1084f48 100644
--- a/can/interfaces/vector/xldefine.py
+++ b/can/interfaces/vector/xldefine.py
@@ -64,7 +64,7 @@ class XL_BusTypes(IntFlag):
XL_BUS_TYPE_A429 = 8192 # =0x00002000
-class XL_CANFD_BusParams_CanOpMode(IntEnum):
+class XL_CANFD_BusParams_CanOpMode(IntFlag):
XL_BUS_PARAMS_CANOPMODE_CAN20 = 1
XL_BUS_PARAMS_CANOPMODE_CANFD = 2
XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8
@@ -318,3 +318,9 @@ class XL_HardwareType(IntEnum):
XL_HWTYPE_VX1161A = 114
XL_HWTYPE_VX1161B = 115
XL_MAX_HWTYPE = 120
+
+
+class XL_SyncPulseSource(IntEnum):
+ XL_SYNC_PULSE_EXTERNAL = 0
+ XL_SYNC_PULSE_OUR = 1
+ XL_SYNC_PULSE_OUR_SHARED = 2
diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py
index 3243fa4a0..8df39e9dc 100644
--- a/can/interfaces/vector/xldriver.py
+++ b/can/interfaces/vector/xldriver.py
@@ -272,3 +272,18 @@ def check_status_initialization(result, function, arguments):
xlCanGetEventString = _xlapi_dll.xlCanGetEventString
xlCanGetEventString.argtypes = [ctypes.POINTER(xlclass.XLcanRxEvent)]
xlCanGetEventString.restype = xlclass.XLstringType
+
+xlGetReceiveQueueLevel = _xlapi_dll.xlGetReceiveQueueLevel
+xlGetReceiveQueueLevel.argtypes = [xlclass.XLportHandle, ctypes.POINTER(ctypes.c_int)]
+xlGetReceiveQueueLevel.restype = xlclass.XLstatus
+xlGetReceiveQueueLevel.errcheck = check_status_operation
+
+xlGenerateSyncPulse = _xlapi_dll.xlGenerateSyncPulse
+xlGenerateSyncPulse.argtypes = [xlclass.XLportHandle, xlclass.XLaccess]
+xlGenerateSyncPulse.restype = xlclass.XLstatus
+xlGenerateSyncPulse.errcheck = check_status_operation
+
+xlFlushReceiveQueue = _xlapi_dll.xlFlushReceiveQueue
+xlFlushReceiveQueue.argtypes = [xlclass.XLportHandle]
+xlFlushReceiveQueue.restype = xlclass.XLstatus
+xlFlushReceiveQueue.errcheck = check_status_operation
From 1e11f21189a297cc08c3e24df52dff9f762a1b60 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 11 Sep 2022 20:14:52 +0200
Subject: [PATCH 138/475] VectorBus init refactoring (#1389)
* refactor VectorBus.__init__()
* move bitrate methods below __init__(), fix typo
* refactor channel index search into method '_find_global_channel_idx', improve error messages
---
can/interfaces/vector/canlib.py | 316 ++++++++++++++++++++------------
1 file changed, 200 insertions(+), 116 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 9cecaa83d..333380a6f 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -21,6 +21,7 @@
Any,
Dict,
Callable,
+ cast,
)
WaitForSingleObject: Optional[Callable[[int, int], int]]
@@ -43,7 +44,7 @@
deprecated_args_alias,
time_perfcounter_correlation,
)
-from can.typechecking import AutoDetectedConfig, CanFilters, Channel
+from can.typechecking import AutoDetectedConfig, CanFilters
# Define Module Logger
# ====================
@@ -152,6 +153,7 @@ def __init__(
if xldriver is None:
raise CanInterfaceNotImplementedError("The Vector API has not been loaded")
self.xldriver = xldriver # keep reference so mypy knows it is not None
+ self.xldriver.xlOpenDriver()
self.poll_interval = poll_interval
@@ -165,7 +167,7 @@ def __init__(
self.channels = [int(ch) for ch in channel]
else:
raise TypeError(
- f"Invalid type for channels parameter: {type(channel).__name__}"
+ f"Invalid type for parameter 'channel': {type(channel).__name__}"
)
self._app_name = app_name.encode() if app_name is not None else b""
@@ -174,136 +176,71 @@ def __init__(
", ".join(f"CAN {ch + 1}" for ch in self.channels),
)
- if serial is not None:
- app_name = None
- channel_index = []
- channel_configs = get_channel_configs()
- for channel_config in channel_configs:
- if channel_config.serialNumber == serial:
- if channel_config.hwChannel in self.channels:
- channel_index.append(channel_config.channelIndex)
- if channel_index:
- if len(channel_index) != len(self.channels):
- LOG.info(
- "At least one defined channel wasn't found on the specified hardware."
- )
- self.channels = channel_index
- else:
- # Is there any better way to raise the error?
- raise CanInitializationError(
- "None of the configured channels could be found on the specified hardware."
- )
+ channel_configs = get_channel_configs()
- self.xldriver.xlOpenDriver()
- self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE)
self.mask = 0
self.fd = fd
- # Get channels masks
- self.channel_masks: Dict[Optional[Channel], int] = {}
- self.index_to_channel = {}
+ self.channel_masks: Dict[int, int] = {}
+ self.index_to_channel: Dict[int, int] = {}
for channel in self.channels:
- if app_name:
- # Get global channel index from application channel
- hw_type, hw_index, hw_channel = self.get_application_config(
- app_name, channel
- )
- LOG.debug("Channel index %d found", channel)
- idx = self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel)
- if idx < 0:
- # Undocumented behavior! See issue #353.
- # If hardware is unavailable, this function returns -1.
- # Raise an exception as if the driver
- # would have signalled XL_ERR_HW_NOT_PRESENT.
- raise VectorInitializationError(
- xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT,
- xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name,
- "xlGetChannelIndex",
- )
- else:
- # Channel already given as global channel
- idx = channel
- mask = 1 << idx
- self.channel_masks[channel] = mask
- self.index_to_channel[idx] = channel
- self.mask |= mask
+ channel_index = self._find_global_channel_idx(
+ channel=channel,
+ serial=serial,
+ app_name=app_name,
+ channel_configs=channel_configs,
+ )
+ LOG.debug("Channel index %d found", channel)
+
+ channel_mask = 1 << channel_index
+ self.channel_masks[channel] = channel_mask
+ self.index_to_channel[channel_index] = channel
+ self.mask |= channel_mask
permission_mask = xlclass.XLaccess()
# Set mask to request channel init permission if needed
if bitrate or fd:
permission_mask.value = self.mask
- if fd:
- self.xldriver.xlOpenPort(
- self.port_handle,
- self._app_name,
- self.mask,
- permission_mask,
- rx_queue_size,
- xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4,
- xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
- )
- else:
- self.xldriver.xlOpenPort(
- self.port_handle,
- self._app_name,
- self.mask,
- permission_mask,
- rx_queue_size,
- xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION,
- xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
- )
+
+ interface_version = (
+ xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4
+ if fd
+ else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION
+ )
+
+ self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE)
+ self.xldriver.xlOpenPort(
+ self.port_handle,
+ self._app_name,
+ self.mask,
+ permission_mask,
+ rx_queue_size,
+ interface_version,
+ xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
+ )
+
LOG.debug(
"Open Port: PortHandle: %d, PermissionMask: 0x%X",
self.port_handle.value,
permission_mask.value,
)
- if permission_mask.value == self.mask:
- if fd:
- self.canFdConf = xlclass.XLcanFdConf()
- if bitrate:
- self.canFdConf.arbitrationBitRate = int(bitrate)
- else:
- self.canFdConf.arbitrationBitRate = 500000
- self.canFdConf.sjwAbr = int(sjw_abr)
- self.canFdConf.tseg1Abr = int(tseg1_abr)
- self.canFdConf.tseg2Abr = int(tseg2_abr)
- if data_bitrate:
- self.canFdConf.dataBitRate = int(data_bitrate)
- else:
- self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate
- self.canFdConf.sjwDbr = int(sjw_dbr)
- self.canFdConf.tseg1Dbr = int(tseg1_dbr)
- self.canFdConf.tseg2Dbr = int(tseg2_dbr)
-
- self.xldriver.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:
- self.xldriver.xlCanSetChannelBitrate(
- self.port_handle, permission_mask, bitrate
+ for channel in self.channels:
+ if permission_mask.value & self.channel_masks[channel]:
+ if fd:
+ self._set_bitrate_canfd(
+ channel=channel,
+ bitrate=bitrate,
+ data_bitrate=data_bitrate,
+ sjw_abr=sjw_abr,
+ tseg1_abr=tseg1_abr,
+ tseg2_abr=tseg2_abr,
+ sjw_dbr=sjw_dbr,
+ tseg1_dbr=tseg1_dbr,
+ tseg2_dbr=tseg2_dbr,
)
- LOG.info("SetChannelBitrate: baudr.=%u", bitrate)
- else:
- LOG.info("No init access!")
+ elif bitrate:
+ self._set_bitrate_can(channel=channel, bitrate=bitrate)
# Enable/disable TX receipts
tx_receipts = 1 if receive_own_messages else 0
@@ -348,6 +285,153 @@ def __init__(
self._is_filtered = False
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
+ def _find_global_channel_idx(
+ self,
+ channel: int,
+ serial: Optional[int],
+ app_name: Optional[str],
+ channel_configs: List["VectorChannelConfig"],
+ ) -> int:
+ if serial is not None:
+ hw_type: Optional[xldefine.XL_HardwareType] = None
+ for channel_config in channel_configs:
+ if channel_config.serialNumber != serial:
+ continue
+
+ hw_type = xldefine.XL_HardwareType(channel_config.hwType)
+ if channel_config.hwChannel == channel:
+ return channel_config.channelIndex
+
+ if hw_type is None:
+ err_msg = f"No interface with serial {serial} found."
+ else:
+ err_msg = f"Channel {channel} not found on interface {hw_type.name} ({serial})."
+ raise CanInitializationError(
+ err_msg, error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT
+ )
+
+ if app_name:
+ hw_type, hw_index, hw_channel = self.get_application_config(
+ app_name, channel
+ )
+ idx = cast(
+ int, self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel)
+ )
+ if idx < 0:
+ # Undocumented behavior! See issue #353.
+ # If hardware is unavailable, this function returns -1.
+ # Raise an exception as if the driver
+ # would have signalled XL_ERR_HW_NOT_PRESENT.
+ raise VectorInitializationError(
+ xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT,
+ xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name,
+ "xlGetChannelIndex",
+ )
+ return idx
+
+ # check if channel is a valid global channel index
+ for channel_config in channel_configs:
+ if channel == channel_config.channelIndex:
+ return channel
+
+ raise CanInitializationError(
+ f"Channel {channel} not found. The 'channel' parameter must be "
+ f"a valid global channel index if neither 'app_name' nor 'serial' were given.",
+ error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT,
+ )
+
+ def _set_bitrate_can(
+ self,
+ channel: int,
+ bitrate: int,
+ sjw: Optional[int] = None,
+ tseg1: Optional[int] = None,
+ tseg2: Optional[int] = None,
+ sam: int = 1,
+ ) -> None:
+ kwargs = [sjw, tseg1, tseg2]
+ if any(kwargs) and not all(kwargs):
+ raise ValueError(
+ f"Either all of sjw, tseg1, tseg2 must be set or none of them."
+ )
+
+ # set parameters if channel has init access
+ if any(kwargs):
+ chip_params = xlclass.XLchipParams()
+ chip_params.bitRate = bitrate
+ chip_params.sjw = sjw
+ chip_params.tseg1 = tseg1
+ chip_params.tseg2 = tseg2
+ chip_params.sam = sam
+ self.xldriver.xlCanSetChannelParams(
+ self.port_handle,
+ self.channel_masks[channel],
+ chip_params,
+ )
+ LOG.info(
+ "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
+ chip_params.bitRate,
+ chip_params.sjw,
+ chip_params.tseg1,
+ chip_params.tseg2,
+ )
+ else:
+ self.xldriver.xlCanSetChannelBitrate(
+ self.port_handle,
+ self.channel_masks[channel],
+ bitrate,
+ )
+ LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate)
+
+ def _set_bitrate_canfd(
+ self,
+ channel: int,
+ bitrate: Optional[int] = None,
+ data_bitrate: Optional[int] = None,
+ sjw_abr: int = 2,
+ tseg1_abr: int = 6,
+ tseg2_abr: int = 3,
+ sjw_dbr: int = 2,
+ tseg1_dbr: int = 6,
+ tseg2_dbr: int = 3,
+ ) -> None:
+ # set parameters if channel has init access
+ canfd_conf = xlclass.XLcanFdConf()
+ if bitrate:
+ canfd_conf.arbitrationBitRate = int(bitrate)
+ else:
+ canfd_conf.arbitrationBitRate = 500_000
+ canfd_conf.sjwAbr = int(sjw_abr)
+ canfd_conf.tseg1Abr = int(tseg1_abr)
+ canfd_conf.tseg2Abr = int(tseg2_abr)
+ if data_bitrate:
+ canfd_conf.dataBitRate = int(data_bitrate)
+ else:
+ canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate)
+ canfd_conf.sjwDbr = int(sjw_dbr)
+ canfd_conf.tseg1Dbr = int(tseg1_dbr)
+ canfd_conf.tseg2Dbr = int(tseg2_dbr)
+ self.xldriver.xlCanFdSetConfiguration(
+ self.port_handle, self.channel_masks[channel], canfd_conf
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u",
+ canfd_conf.arbitrationBitRate,
+ canfd_conf.dataBitRate,
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
+ canfd_conf.sjwAbr,
+ canfd_conf.tseg1Abr,
+ canfd_conf.tseg2Abr,
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u",
+ canfd_conf.sjwDbr,
+ canfd_conf.tseg1Dbr,
+ canfd_conf.tseg2Dbr,
+ )
+
def _apply_filters(self, filters: Optional[CanFilters]) -> None:
if filters:
# Only up to one filter per ID type allowed
@@ -544,7 +628,7 @@ def _send_sequence(self, msgs: Sequence[Message]) -> int:
def _get_tx_channel_mask(self, msgs: Sequence[Message]) -> int:
if len(msgs) == 1:
- return self.channel_masks.get(msgs[0].channel, self.mask)
+ return self.channel_masks.get(msgs[0].channel, self.mask) # type: ignore[arg-type]
else:
return self.mask
From 366e2391a517c3b828e90925508a9ea3c1bfd6f2 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 12 Sep 2022 15:02:58 +0200
Subject: [PATCH 139/475] Test on vector virtual bus if XL API is available
(#1390)
* refactoring for easier testing
* use the vector virtual bus if XL driver is available
* check xldriver to satisfy mypy
* fix assertion
---
can/interfaces/vector/canlib.py | 21 +-
test/test_vector.py | 1118 ++++++++++++++++++++++---------
2 files changed, 802 insertions(+), 337 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 333380a6f..ae60c5754 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -881,16 +881,25 @@ class VectorChannelConfig(NamedTuple):
transceiverName: str
-def get_channel_configs() -> List[VectorChannelConfig]:
+def _get_xl_driver_config() -> xlclass.XLdriverConfig:
if xldriver is None:
- return []
+ raise VectorError(
+ error_code=xldefine.XL_Status.XL_ERR_DLL_NOT_FOUND,
+ error_string="xldriver is unavailable",
+ function="_get_xl_driver_config",
+ )
driver_config = xlclass.XLdriverConfig()
+ xldriver.xlOpenDriver()
+ xldriver.xlGetDriverConfig(driver_config)
+ xldriver.xlCloseDriver()
+ return driver_config
+
+
+def get_channel_configs() -> List[VectorChannelConfig]:
try:
- xldriver.xlOpenDriver()
- xldriver.xlGetDriverConfig(driver_config)
- xldriver.xlCloseDriver()
+ driver_config = _get_xl_driver_config()
except VectorError:
- pass
+ return []
channel_list: List[VectorChannelConfig] = []
for i in range(driver_config.channelCount):
diff --git a/test/test_vector.py b/test/test_vector.py
index 338783136..c4ae21f4e 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -5,9 +5,9 @@
"""
import ctypes
-import os
+import functools
import pickle
-import unittest
+import time
from unittest.mock import Mock
import pytest
@@ -24,332 +24,792 @@
)
from test.config import IS_WINDOWS
+XLDRIVER_FOUND = canlib.xldriver is not None
-class TestVectorBus(unittest.TestCase):
- def setUp(self) -> None:
- # basic mock for XLDriver
- can.interfaces.vector.canlib.xldriver = Mock()
-
- # bus creation functions
- can.interfaces.vector.canlib.xldriver.xlOpenDriver = Mock()
- can.interfaces.vector.canlib.xldriver.xlGetApplConfig = Mock(
- side_effect=xlGetApplConfig
- )
- can.interfaces.vector.canlib.xldriver.xlGetChannelIndex = Mock(
- side_effect=xlGetChannelIndex
- )
- can.interfaces.vector.canlib.xldriver.xlOpenPort = Mock(side_effect=xlOpenPort)
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration = Mock(
- return_value=0
- )
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelMode = Mock(return_value=0)
- can.interfaces.vector.canlib.xldriver.xlActivateChannel = Mock(return_value=0)
- can.interfaces.vector.canlib.xldriver.xlGetSyncTime = Mock(
- side_effect=xlGetSyncTime
- )
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelAcceptance = Mock(
- return_value=0
- )
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate = Mock(
- return_value=0
- )
- can.interfaces.vector.canlib.xldriver.xlSetNotification = Mock(
- side_effect=xlSetNotification
- )
-
- # bus deactivation functions
- can.interfaces.vector.canlib.xldriver.xlDeactivateChannel = Mock(return_value=0)
- can.interfaces.vector.canlib.xldriver.xlClosePort = Mock(return_value=0)
- can.interfaces.vector.canlib.xldriver.xlCloseDriver = Mock()
-
- # sender functions
- can.interfaces.vector.canlib.xldriver.xlCanTransmit = Mock(return_value=0)
- can.interfaces.vector.canlib.xldriver.xlCanTransmitEx = Mock(return_value=0)
-
- # various functions
- can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue = Mock()
- can.interfaces.vector.canlib.WaitForSingleObject = Mock()
-
- self.bus = None
-
- def tearDown(self) -> None:
- if self.bus:
- self.bus.shutdown()
- self.bus = None
-
- def test_bus_creation(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.assertIsInstance(self.bus, canlib.VectorBus)
- can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
- can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
-
- can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
- xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
- self.assertEqual(
- xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
- )
- self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value)
-
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
-
- def test_bus_creation_bitrate(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", bitrate=200000, _testing=True)
- self.assertIsInstance(self.bus, canlib.VectorBus)
- can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
- can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
-
- can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
- xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
- self.assertEqual(
- xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
- )
- self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value)
-
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called()
- xlCanSetChannelBitrate_args = (
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0]
- )
- self.assertEqual(xlCanSetChannelBitrate_args[2], 200000)
-
- def test_bus_creation_fd(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
- self.assertIsInstance(self.bus, canlib.VectorBus)
- can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
- can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
-
- can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
- xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
- self.assertEqual(
- xlOpenPort_args[5],
- xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value,
- )
- self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value)
-
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
-
- def test_bus_creation_fd_bitrate_timings(self) -> None:
- self.bus = can.Bus(
- channel=0,
- bustype="vector",
- fd=True,
- bitrate=500000,
- data_bitrate=2000000,
- sjw_abr=10,
- tseg1_abr=11,
- tseg2_abr=12,
- sjw_dbr=13,
- tseg1_dbr=14,
- tseg2_dbr=15,
- _testing=True,
- )
- self.assertIsInstance(self.bus, canlib.VectorBus)
- can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
- can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
-
- can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
- xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
- self.assertEqual(
- xlOpenPort_args[5],
- xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value,
- )
- self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value)
-
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
-
- xlCanFdSetConfiguration_args = (
- can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0]
- )
- canFdConf = xlCanFdSetConfiguration_args[2]
- self.assertEqual(canFdConf.arbitrationBitRate, 500000)
- self.assertEqual(canFdConf.dataBitRate, 2000000)
- self.assertEqual(canFdConf.sjwAbr, 10)
- self.assertEqual(canFdConf.tseg1Abr, 11)
- self.assertEqual(canFdConf.tseg2Abr, 12)
- self.assertEqual(canFdConf.sjwDbr, 13)
- self.assertEqual(canFdConf.tseg1Dbr, 14)
- self.assertEqual(canFdConf.tseg2Dbr, 15)
-
- def test_receive(self) -> None:
- can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive)
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.bus.recv(timeout=0.05)
- can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called()
-
- def test_receive_fd(self) -> None:
- can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(
- side_effect=xlCanReceive
- )
- self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
- self.bus.recv(timeout=0.05)
- can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
- can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called()
-
- def test_receive_non_msg_event(self) -> None:
- can.interfaces.vector.canlib.xldriver.xlReceive = Mock(
- side_effect=xlReceive_chipstate
- )
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.bus.handle_can_event = Mock()
- self.bus.recv(timeout=0.05)
- can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called()
- self.bus.handle_can_event.assert_called()
-
- def test_receive_fd_non_msg_event(self) -> None:
- can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(
- side_effect=xlCanReceive_chipstate
- )
- self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
- self.bus.handle_canfd_event = Mock()
- self.bus.recv(timeout=0.05)
- can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
- can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called()
- self.bus.handle_canfd_event.assert_called()
-
- def test_send(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- msg = can.Message(
- arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
- )
- self.bus.send(msg)
- can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called()
-
- def test_send_fd(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
- msg = can.Message(
- arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
- )
- self.bus.send(msg)
- can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called()
- can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called()
-
- def test_flush_tx_buffer(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.bus.flush_tx_buffer()
- can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called()
-
- def test_shutdown(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.bus.shutdown()
- can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
- can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called()
- can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called()
-
- def test_reset(self) -> None:
- self.bus = can.Bus(channel=0, bustype="vector", _testing=True)
- self.bus.reset()
- can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
- can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called()
-
- def test_popup_hw_cfg(self) -> None:
- canlib.xldriver.xlPopupHwConfig = Mock()
- canlib.VectorBus.popup_vector_hw_configuration(10)
- assert canlib.xldriver.xlPopupHwConfig.called
- args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args
- assert isinstance(args[0], ctypes.c_char_p)
- assert isinstance(args[1], ctypes.c_uint)
-
- def test_get_application_config(self) -> None:
- canlib.xldriver.xlGetApplConfig = Mock()
- canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0)
- assert canlib.xldriver.xlGetApplConfig.called
-
- def test_set_application_config(self) -> None:
- canlib.xldriver.xlSetApplConfig = Mock()
- canlib.VectorBus.set_application_config(
- app_name="CANalyzer",
- app_channel=0,
- hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610,
- hw_index=0,
- hw_channel=0,
- )
- assert canlib.xldriver.xlSetApplConfig.called
-
- def test_set_timer_rate(self) -> None:
- canlib.xldriver.xlSetTimerRate = Mock()
- bus: canlib.VectorBus = can.Bus(
- channel=0, bustype="vector", fd=True, _testing=True
- )
- bus.set_timer_rate(timer_rate_ms=1)
- assert canlib.xldriver.xlSetTimerRate.called
-
- def test_called_without_testing_argument(self) -> None:
- """This tests if an exception is thrown when we are not running on Windows."""
- if os.name != "nt":
- with self.assertRaises(can.CanInterfaceNotImplementedError):
- # do not set the _testing argument, since it would suppress the exception
- can.Bus(channel=0, bustype="vector")
-
- def test_vector_error_pickle(self) -> None:
- for error_type in [
- VectorError,
- VectorInitializationError,
- VectorOperationError,
- ]:
- with self.subTest(f"error_type = {error_type.__name__}"):
-
- error_code = 118
- error_string = "XL_ERROR"
- function = "function_name"
-
- exc = error_type(error_code, error_string, function)
-
- # pickle and unpickle
- p = pickle.dumps(exc)
- exc_unpickled: VectorError = pickle.loads(p)
-
- self.assertEqual(str(exc), str(exc_unpickled))
- self.assertEqual(error_code, exc_unpickled.error_code)
-
- with pytest.raises(error_type):
- raise exc_unpickled
-
- def test_vector_subtype_error_from_generic(self) -> None:
- for error_type in [VectorInitializationError, VectorOperationError]:
- with self.subTest(f"error_type = {error_type.__name__}"):
-
- error_code = 118
- error_string = "XL_ERROR"
- function = "function_name"
-
- generic = VectorError(error_code, error_string, function)
-
- # pickle and unpickle
- specific: VectorError = error_type.from_generic(generic)
-
- self.assertEqual(str(generic), str(specific))
- self.assertEqual(error_code, specific.error_code)
-
- with pytest.raises(error_type):
- raise specific
-
- @unittest.skipUnless(IS_WINDOWS, "Windows specific test")
- def test_winapi_availability(self) -> None:
- self.assertIsNotNone(canlib.WaitForSingleObject)
- self.assertIsNotNone(canlib.INFINITE)
-
-
-class TestVectorChannelConfig:
- def test_attributes(self):
- assert hasattr(VectorChannelConfig, "name")
- assert hasattr(VectorChannelConfig, "hwType")
- assert hasattr(VectorChannelConfig, "hwIndex")
- assert hasattr(VectorChannelConfig, "hwChannel")
- assert hasattr(VectorChannelConfig, "channelIndex")
- assert hasattr(VectorChannelConfig, "channelMask")
- assert hasattr(VectorChannelConfig, "channelCapabilities")
- assert hasattr(VectorChannelConfig, "channelBusCapabilities")
- assert hasattr(VectorChannelConfig, "isOnBus")
- assert hasattr(VectorChannelConfig, "connectedBusType")
- assert hasattr(VectorChannelConfig, "serialNumber")
- assert hasattr(VectorChannelConfig, "articleNumber")
- assert hasattr(VectorChannelConfig, "transceiverName")
+
+@pytest.fixture()
+def mock_xldriver() -> None:
+ # basic mock for XLDriver
+ xldriver_mock = Mock()
+
+ # bus creation functions
+ xldriver_mock.xlOpenDriver = Mock()
+ xldriver_mock.xlGetApplConfig = Mock(side_effect=xlGetApplConfig)
+ xldriver_mock.xlGetChannelIndex = Mock(side_effect=xlGetChannelIndex)
+ xldriver_mock.xlOpenPort = Mock(side_effect=xlOpenPort)
+ xldriver_mock.xlCanFdSetConfiguration = Mock(return_value=0)
+ xldriver_mock.xlCanSetChannelMode = Mock(return_value=0)
+ xldriver_mock.xlActivateChannel = Mock(return_value=0)
+ xldriver_mock.xlGetSyncTime = Mock(side_effect=xlGetSyncTime)
+ xldriver_mock.xlCanSetChannelAcceptance = Mock(return_value=0)
+ xldriver_mock.xlCanSetChannelBitrate = Mock(return_value=0)
+ xldriver_mock.xlSetNotification = Mock(side_effect=xlSetNotification)
+
+ # bus deactivation functions
+ xldriver_mock.xlDeactivateChannel = Mock(return_value=0)
+ xldriver_mock.xlClosePort = Mock(return_value=0)
+ xldriver_mock.xlCloseDriver = Mock()
+
+ # sender functions
+ xldriver_mock.xlCanTransmit = Mock(return_value=0)
+ xldriver_mock.xlCanTransmitEx = Mock(return_value=0)
+
+ # various functions
+ xldriver_mock.xlCanFlushTransmitQueue = Mock()
+
+ # backup unmodified values
+ real_xldriver = canlib.xldriver
+ real_waitforsingleobject = canlib.WaitForSingleObject
+
+ # set mock
+ canlib.xldriver = xldriver_mock
+ canlib.HAS_EVENTS = False
+
+ yield
+
+ # cleanup
+ canlib.xldriver = real_xldriver
+ canlib.WaitForSingleObject = real_waitforsingleobject
+
+
+def test_bus_creation_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation() -> None:
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ assert isinstance(bus, canlib.VectorBus)
+ bus.shutdown()
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert bus.channel_masks[0] == xl_channel_config.channelMask
+ assert (
+ xl_channel_config.busParams.data.can.canOpMode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20
+ )
+
+ bus = canlib.VectorBus(channel=0, serial=_find_virtual_can_serial())
+ assert isinstance(bus, canlib.VectorBus)
+ bus.shutdown()
+
+
+def test_bus_creation_bitrate_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", bitrate=200_000, _testing=True)
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called()
+ xlCanSetChannelBitrate_args = (
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0]
+ )
+ assert xlCanSetChannelBitrate_args[2] == 200_000
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation_bitrate() -> None:
+ bus = can.Bus(
+ channel=0, serial=_find_virtual_can_serial(), bustype="vector", bitrate=200_000
+ )
+ assert isinstance(bus, canlib.VectorBus)
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert xl_channel_config.busParams.data.can.bitRate == 200_000
+
+ bus.shutdown()
+
+
+def test_bus_creation_fd_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert (
+ xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value
+ )
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation_fd() -> None:
+ bus = can.Bus(
+ channel=0, serial=_find_virtual_can_serial(), bustype="vector", fd=True
+ )
+ assert isinstance(bus, canlib.VectorBus)
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert (
+ xl_channel_config.interfaceVersion
+ == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4
+ )
+ assert (
+ xl_channel_config.busParams.data.canFD.canOpMode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
+ )
+ bus.shutdown()
+
+
+def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None:
+ bus = can.Bus(
+ channel=0,
+ bustype="vector",
+ fd=True,
+ bitrate=500_000,
+ data_bitrate=2_000_000,
+ sjw_abr=10,
+ tseg1_abr=11,
+ tseg2_abr=12,
+ sjw_dbr=13,
+ tseg1_dbr=14,
+ tseg2_dbr=15,
+ _testing=True,
+ )
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert (
+ xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value
+ )
+
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
+
+ xlCanFdSetConfiguration_args = (
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0]
+ )
+ canFdConf = xlCanFdSetConfiguration_args[2]
+ assert canFdConf.arbitrationBitRate == 500000
+ assert canFdConf.dataBitRate == 2000000
+ assert canFdConf.sjwAbr == 10
+ assert canFdConf.tseg1Abr == 11
+ assert canFdConf.tseg2Abr == 12
+ assert canFdConf.sjwDbr == 13
+ assert canFdConf.tseg1Dbr == 14
+ assert canFdConf.tseg2Dbr == 15
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation_fd_bitrate_timings() -> None:
+ bus = can.Bus(
+ channel=0,
+ serial=_find_virtual_can_serial(),
+ bustype="vector",
+ fd=True,
+ bitrate=500_000,
+ data_bitrate=2_000_000,
+ sjw_abr=10,
+ tseg1_abr=11,
+ tseg2_abr=12,
+ sjw_dbr=13,
+ tseg1_dbr=14,
+ tseg2_dbr=15,
+ )
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert (
+ xl_channel_config.interfaceVersion
+ == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4
+ )
+ assert (
+ xl_channel_config.busParams.data.canFD.canOpMode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
+ )
+ assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000
+ assert xl_channel_config.busParams.data.canFD.sjwAbr == 10
+ assert xl_channel_config.busParams.data.canFD.tseg1Abr == 11
+ assert xl_channel_config.busParams.data.canFD.tseg2Abr == 12
+ assert xl_channel_config.busParams.data.canFD.sjwDbr == 13
+ assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 14
+ assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 15
+ assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000
+
+ bus.shutdown()
+
+
+def test_send_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ msg = can.Message(
+ arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
+ )
+ bus.send(msg)
+ can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called()
+
+
+def test_send_fd_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ msg = can.Message(
+ arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
+ )
+ bus.send(msg)
+ can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called()
+
+
+def test_receive_mocked(mock_xldriver) -> None:
+ can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive)
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus.recv(timeout=0.05)
+ can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called()
+
+
+def test_receive_fd_mocked(mock_xldriver) -> None:
+ can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(side_effect=xlCanReceive)
+ bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus.recv(timeout=0.05)
+ can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_send_and_receive() -> None:
+ bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+
+ msg_std = can.Message(
+ channel=0, arbitration_id=0xFF, data=list(range(8)), is_extended_id=False
+ )
+ msg_ext = can.Message(
+ channel=0, arbitration_id=0xFFFFFF, data=list(range(8)), is_extended_id=True
+ )
+
+ bus1.send(msg_std)
+ msg_std_recv = bus2.recv(None)
+ assert msg_std.equals(msg_std_recv, timestamp_delta=None)
+
+ bus1.send(msg_ext)
+ msg_ext_recv = bus2.recv(None)
+ assert msg_ext.equals(msg_ext_recv, timestamp_delta=None)
+
+ bus1.shutdown()
+ bus2.shutdown()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_send_and_receive_fd() -> None:
+ bus1 = can.Bus(
+ channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ )
+ bus2 = can.Bus(
+ channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ )
+
+ msg_std = can.Message(
+ channel=0,
+ arbitration_id=0xFF,
+ data=list(range(64)),
+ is_extended_id=False,
+ is_fd=True,
+ )
+ msg_ext = can.Message(
+ channel=0,
+ arbitration_id=0xFFFFFF,
+ data=list(range(64)),
+ is_extended_id=True,
+ is_fd=True,
+ )
+
+ bus1.send(msg_std)
+ msg_std_recv = bus2.recv(None)
+ assert msg_std.equals(msg_std_recv, timestamp_delta=None)
+
+ bus1.send(msg_ext)
+ msg_ext_recv = bus2.recv(None)
+ assert msg_ext.equals(msg_ext_recv, timestamp_delta=None)
+
+ bus1.shutdown()
+ bus2.shutdown()
+
+
+def test_receive_non_msg_event_mocked(mock_xldriver) -> None:
+ can.interfaces.vector.canlib.xldriver.xlReceive = Mock(
+ side_effect=xlReceive_chipstate
+ )
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus.handle_can_event = Mock()
+ bus.recv(timeout=0.05)
+ can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called()
+ bus.handle_can_event.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_receive_non_msg_event() -> None:
+ bus = canlib.VectorBus(
+ channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ )
+ bus.handle_can_event = Mock()
+ bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0])
+ bus.recv(timeout=0.5)
+ bus.handle_can_event.assert_called()
+ bus.shutdown()
+
+
+def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None:
+ can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(
+ side_effect=xlCanReceive_chipstate
+ )
+ bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus.handle_canfd_event = Mock()
+ bus.recv(timeout=0.05)
+ can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called()
+ bus.handle_canfd_event.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_receive_fd_non_msg_event() -> None:
+ bus = canlib.VectorBus(
+ channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ )
+ bus.handle_canfd_event = Mock()
+ bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0])
+ bus.recv(timeout=0.5)
+ bus.handle_canfd_event.assert_called()
+ bus.shutdown()
+
+
+def test_flush_tx_buffer_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus.flush_tx_buffer()
+ can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_flush_tx_buffer() -> None:
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus.flush_tx_buffer()
+ bus.shutdown()
+
+
+def test_shutdown_mocked(mock_xldriver) -> None:
+ bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus.shutdown()
+ can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_shutdown() -> None:
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert xl_channel_config.isOnBus != 0
+ bus.shutdown()
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert xl_channel_config.isOnBus == 0
+
+
+def test_reset_mocked(mock_xldriver) -> None:
+ bus = canlib.VectorBus(channel=0, bustype="vector", _testing=True)
+ bus.reset()
+ can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called()
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_reset_mocked() -> None:
+ bus = canlib.VectorBus(
+ channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ )
+ bus.reset()
+ bus.shutdown()
+
+
+def test_popup_hw_cfg_mocked(mock_xldriver) -> None:
+ canlib.xldriver.xlPopupHwConfig = Mock()
+ canlib.VectorBus.popup_vector_hw_configuration(10)
+ assert canlib.xldriver.xlPopupHwConfig.called
+ args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args
+ assert isinstance(args[0], ctypes.c_char_p)
+ assert isinstance(args[1], ctypes.c_uint)
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_popup_hw_cfg() -> None:
+ with pytest.raises(VectorOperationError):
+ canlib.VectorBus.popup_vector_hw_configuration(1)
+
+
+def test_get_application_config_mocked(mock_xldriver) -> None:
+ canlib.xldriver.xlGetApplConfig = Mock()
+ canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0)
+ assert canlib.xldriver.xlGetApplConfig.called
+
+
+def test_set_application_config_mocked(mock_xldriver) -> None:
+ canlib.xldriver.xlSetApplConfig = Mock()
+ canlib.VectorBus.set_application_config(
+ app_name="CANalyzer",
+ app_channel=0,
+ hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610,
+ hw_index=0,
+ hw_channel=0,
+ )
+ assert canlib.xldriver.xlSetApplConfig.called
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_set_and_get_application_config() -> None:
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=1
+ )
+ canlib.VectorBus.set_application_config(
+ app_name="python-can::test_vector",
+ app_channel=5,
+ hw_channel=xl_channel_config.hwChannel,
+ hw_index=xl_channel_config.hwIndex,
+ hw_type=xldefine.XL_HardwareType(xl_channel_config.hwType),
+ )
+ hw_type, hw_index, hw_channel = canlib.VectorBus.get_application_config(
+ app_name="python-can::test_vector",
+ app_channel=5,
+ )
+ assert hw_type == xldefine.XL_HardwareType(xl_channel_config.hwType)
+ assert hw_index == xl_channel_config.hwIndex
+ assert hw_channel == xl_channel_config.hwChannel
+
+
+def test_set_timer_mocked(mock_xldriver) -> None:
+ canlib.xldriver.xlSetTimerRate = Mock()
+ bus = canlib.VectorBus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus.set_timer_rate(timer_rate_ms=1)
+ assert canlib.xldriver.xlSetTimerRate.called
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_set_timer() -> None:
+ bus = canlib.VectorBus(
+ channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ )
+ bus.handle_can_event = Mock()
+ bus.set_timer_rate(timer_rate_ms=1)
+ t0 = time.perf_counter()
+ while time.perf_counter() - t0 < 0.5:
+ bus.recv(timeout=-1)
+
+ # call_count is incorrect when using virtual bus
+ # assert bus.handle_can_event.call_count > 498
+ # assert bus.handle_can_event.call_count < 502
+
+
+@pytest.mark.skipif(IS_WINDOWS, reason="Not relevant for Windows.")
+def test_called_without_testing_argument() -> None:
+ """This tests if an exception is thrown when we are not running on Windows."""
+ with pytest.raises(can.CanInterfaceNotImplementedError):
+ # do not set the _testing argument, since it would suppress the exception
+ can.Bus(channel=0, bustype="vector")
+
+
+def test_vector_error_pickle() -> None:
+ for error_type in [
+ VectorError,
+ VectorInitializationError,
+ VectorOperationError,
+ ]:
+ error_code = 118
+ error_string = "XL_ERROR"
+ function = "function_name"
+
+ exc = error_type(error_code, error_string, function)
+
+ # pickle and unpickle
+ p = pickle.dumps(exc)
+ exc_unpickled: VectorError = pickle.loads(p)
+
+ assert str(exc) == str(exc_unpickled)
+ assert error_code == exc_unpickled.error_code
+
+ with pytest.raises(error_type):
+ raise exc_unpickled
+
+
+def test_vector_subtype_error_from_generic() -> None:
+ for error_type in [VectorInitializationError, VectorOperationError]:
+ error_code = 118
+ error_string = "XL_ERROR"
+ function = "function_name"
+
+ generic = VectorError(error_code, error_string, function)
+
+ # pickle and unpickle
+ specific: VectorError = error_type.from_generic(generic)
+
+ assert str(generic) == str(specific)
+ assert error_code == specific.error_code
+
+ with pytest.raises(error_type):
+ raise specific
+
+
+def test_get_channel_configs() -> None:
+ _original_func = canlib._get_xl_driver_config
+ canlib._get_xl_driver_config = _get_predefined_xl_driver_config
+
+ channel_configs = canlib.get_channel_configs()
+ assert len(channel_configs) == 12
+
+ canlib._get_xl_driver_config = _original_func
+
+
+@pytest.mark.skipif(not IS_WINDOWS, reason="Windows specific test")
+def test_winapi_availability() -> None:
+ assert canlib.WaitForSingleObject is not None
+ assert canlib.INFINITE is not None
+
+
+def test_vector_channel_config_attributes():
+ assert hasattr(VectorChannelConfig, "name")
+ assert hasattr(VectorChannelConfig, "hwType")
+ assert hasattr(VectorChannelConfig, "hwIndex")
+ assert hasattr(VectorChannelConfig, "hwChannel")
+ assert hasattr(VectorChannelConfig, "channelIndex")
+ assert hasattr(VectorChannelConfig, "channelMask")
+ assert hasattr(VectorChannelConfig, "channelCapabilities")
+ assert hasattr(VectorChannelConfig, "channelBusCapabilities")
+ assert hasattr(VectorChannelConfig, "isOnBus")
+ assert hasattr(VectorChannelConfig, "connectedBusType")
+ assert hasattr(VectorChannelConfig, "serialNumber")
+ assert hasattr(VectorChannelConfig, "articleNumber")
+ assert hasattr(VectorChannelConfig, "transceiverName")
+
+
+# *****************************************************************************
+# Utility functions
+# *****************************************************************************
+
+
+def _find_xl_channel_config(serial: int, channel: int) -> xlclass.XLchannelConfig:
+ """Helper function"""
+ xl_driver_config = xlclass.XLdriverConfig()
+ canlib.xldriver.xlOpenDriver()
+ canlib.xldriver.xlGetDriverConfig(xl_driver_config)
+ canlib.xldriver.xlCloseDriver()
+
+ for i in range(xl_driver_config.channelCount):
+ xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i]
+
+ if xl_channel_config.serialNumber != serial:
+ continue
+
+ if xl_channel_config.hwChannel != channel:
+ continue
+
+ return xl_channel_config
+
+ raise LookupError("XLchannelConfig not found.")
+
+
+@functools.lru_cache()
+def _find_virtual_can_serial() -> int:
+ """Serial number might be 0 or 100 depending on driver version."""
+ xl_driver_config = xlclass.XLdriverConfig()
+ canlib.xldriver.xlOpenDriver()
+ canlib.xldriver.xlGetDriverConfig(xl_driver_config)
+ canlib.xldriver.xlCloseDriver()
+
+ for i in range(xl_driver_config.channelCount):
+ xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i]
+
+ if xl_channel_config.transceiverName.decode() == "Virtual CAN":
+ return xl_channel_config.serialNumber
+
+ raise LookupError("Vector virtual CAN not found")
+
+
+XL_DRIVER_CONFIG_EXAMPLE = (
+ b"\x0E\x00\x1E\x14\x0C\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E"
+ b"\x65\x6C\x20\x53\x74\x72\x65\x61\x6D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x2D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04"
+ b"\x0A\x40\x00\x02\x00\x02\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E"
+ b"\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08"
+ b"\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31"
+ b"\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x01\x03\x02\x00\x00\x00\x00\x01\x02\x00\x00"
+ b"\x00\x00\x00\x00\x00\x02\x10\x00\x08\x07\x01\x04\x00\x00\x00\x00\x00\x00\x04\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00"
+ b"\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x46\x52\x70\x69\x67\x67\x79\x20\x31\x30"
+ b"\x38\x30\x41\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x32\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x02\x3C\x01\x00"
+ b"\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x03\x05\x01\x00"
+ b"\x00\x00\x04\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00"
+ b"\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00"
+ b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F\x6E\x20"
+ b"\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28\x48\x69"
+ b"\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"
+ b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E"
+ b"\x6E\x65\x6C\x20\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x2D\x00\x03\x3C\x01\x00\x00\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x12"
+ b"\x00\x00\xA2\x03\x09\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00"
+ b"\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x9B\x00\x00\x00\x68\x89\x09"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00"
+ b"\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00"
+ b"\x08\x1C\x00\x00\x4F\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35"
+ b"\x31\x63\x61\x70\x28\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39"
+ b"\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x34\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x04\x33\x01\x00\x00\x00\x00\x04\x10\x00"
+ b"\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x09\x02\x08\x00\x00\x00\x00\x00\x02"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03"
+ b"\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4C\x49\x4E\x70\x69\x67\x67\x79\x20"
+ b"\x37\x32\x36\x39\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x07\x00\x00\x00\x70\x17\x00\x00\x0C\x09\x03\x04\x58\x02\x10\x0E\x30"
+ b"\x57\x05\x00\x00\x00\x00\x00\x88\x13\x88\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x35\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x05\x00\x00"
+ b"\x00\x00\x02\x00\x05\x20\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00"
+ b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61"
+ b"\x6E\x6E\x65\x6C\x20\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x2D\x00\x06\x00\x00\x00\x00\x02\x00\x06\x40\x00\x00\x00\x00\x00\x00\x00"
+ b"\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00"
+ b"\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38"
+ b"\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x37\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x07\x00\x00\x00\x00\x02\x00\x07\x80"
+ b"\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x38"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x08\x3C"
+ b"\x01\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x01\x00"
+ b"\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01"
+ b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00"
+ b"\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F"
+ b"\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28"
+ b"\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68"
+ b"\x61\x6E\x6E\x65\x6C\x20\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x2D\x00\x09\x80\x02\x00\x00\x00\x00\x09\x00\x02\x00\x00\x00\x00\x00"
+ b"\x00\x02\x00\x00\x00\x40\x00\x40\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03\x00\x00\x00\x00\x00"
+ b"\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03"
+ b"\x00\x00\x08\x1C\x00\x00\x44\x2F\x41\x20\x49\x4F\x70\x69\x67\x67\x79\x20\x38\x36"
+ b"\x34\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69"
+ b"\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x16\x00\x00\x00\x00\x00\x0A"
+ b"\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01\x00\x01\x00\x00\x00\x00\x00"
+ b"\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00"
+ b"\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x1E"
+ b"\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C"
+ b"\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C"
+ b"\x20\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01"
+ b"\x16\x00\x00\x00\x00\x00\x0B\x00\x08\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01"
+ b"\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01"
+ b"\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x10\x00\x1E\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x02" + 11832 * b"\x00"
+)
+
+
+def _get_predefined_xl_driver_config() -> xlclass.XLdriverConfig:
+ return xlclass.XLdriverConfig.from_buffer_copy(XL_DRIVER_CONFIG_EXAMPLE)
+
+
+# *****************************************************************************
+# Mock functions/side effects
+# *****************************************************************************
def xlGetApplConfig(
@@ -454,7 +914,3 @@ def xlCanReceive_chipstate(
event.timeStamp = 0
event.chanIndex = 2
return 0
-
-
-if __name__ == "__main__":
- unittest.main()
From c3a5c7ab969cd40711dba992e6a95ec8df4551b6 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 13 Sep 2022 17:15:38 +0200
Subject: [PATCH 140/475] Provide meaningful error message for xlGetApplConfig
error (#1392)
---
can/interfaces/vector/canlib.py | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index ae60c5754..7737a99d3 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -791,14 +791,24 @@ def get_application_config(
hw_channel = ctypes.c_uint()
_app_channel = ctypes.c_uint(app_channel)
- xldriver.xlGetApplConfig(
- app_name.encode(),
- _app_channel,
- hw_type,
- hw_index,
- hw_channel,
- xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
- )
+ try:
+ xldriver.xlGetApplConfig(
+ app_name.encode(),
+ _app_channel,
+ hw_type,
+ hw_index,
+ hw_channel,
+ xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
+ )
+ except VectorError as e:
+ raise VectorInitializationError(
+ error_code=e.error_code,
+ error_string=(
+ f"Vector HW Config: Channel '{app_channel}' of "
+ f"application '{app_name}' is not assigned to any interface"
+ ),
+ function="xlGetApplConfig",
+ ) from None
return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value
@staticmethod
From b2b2a80486bb2c165b710baed509a6654df76703 Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Tue, 13 Sep 2022 13:27:26 -0500
Subject: [PATCH 141/475] Pass file mode to compress function (#1384)
---
can/io/logger.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index 478651953..ec34079b4 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -88,7 +88,7 @@ def __new__( # type: ignore
file_or_filename: AcceptedIOType = filename
if suffix == ".gz":
- suffix, file_or_filename = Logger.compress(filename)
+ suffix, file_or_filename = Logger.compress(filename, *args, **kwargs)
try:
return Logger.message_writers[suffix](file_or_filename, *args, **kwargs)
@@ -98,13 +98,18 @@ def __new__( # type: ignore
) from None
@staticmethod
- def compress(filename: StringPathLike) -> Tuple[str, FileLike]:
+ def compress(
+ filename: StringPathLike, *args: Any, **kwargs: Any
+ ) -> Tuple[str, FileLike]:
"""
Return the suffix and io object of the decompressed file.
File will automatically recompress upon close.
"""
real_suffix = pathlib.Path(filename).suffixes[-2].lower()
- mode = "ab" if real_suffix == ".blf" else "at"
+ if kwargs.get("append", False):
+ mode = "ab" if real_suffix == ".blf" else "at"
+ else:
+ mode = "wb" if real_suffix == ".blf" else "wt"
return real_suffix, gzip.open(filename, mode)
From 23b6b1916532b6e6ba2e5cc6f2e0d139c428a02a Mon Sep 17 00:00:00 2001
From: Brent Barbachem
Date: Mon, 3 Oct 2022 09:16:57 -0400
Subject: [PATCH 142/475] Update BufferedReader.get_message docstring (#1397)
** Docstring now states that the `get_message` method utilizes a FIFO
ordering for grabbing messages from the queue.
---
can/listener.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/can/listener.py b/can/listener.py
index 12836a83c..6c9cdf0be 100644
--- a/can/listener.py
+++ b/can/listener.py
@@ -105,13 +105,13 @@ def on_message_received(self, msg: Message) -> None:
def get_message(self, timeout: float = 0.5) -> Optional[Message]:
"""
- Attempts to retrieve the latest message received by the instance. If no message is
- available it blocks for given timeout or until a message is received, or else
- returns None (whichever is shorter). This method does not block after
- :meth:`can.BufferedReader.stop` has been called.
+ Attempts to retrieve the message that has been in the queue for the longest amount
+ of time (FIFO). If no message is available, it blocks for given timeout or until a
+ message is received (whichever is shorter), or else returns None. This method does
+ not block after :meth:`can.BufferedReader.stop` has been called.
:param timeout: The number of seconds to wait for a new message.
- :return: the Message if there is one, or None if there is not.
+ :return: the received :class:`can.Message` or `None`, if the queue is empty.
"""
try:
if self.is_stopped:
From 89c395fd315179b1eb2ca8e21a852c825bb385b8 Mon Sep 17 00:00:00 2001
From: Nazia Povey
Date: Mon, 3 Oct 2022 09:43:41 -0400
Subject: [PATCH 143/475] Move windows-curses dependency to an optional extra
(#1395)
* Move windows-curses dependency to an optional extra
Python 3.11 wheels for windows-curses are [not yet available][1], and this
meant that python-can could not be installed on windows with Python
3.11. Since windows-curses is only used in viewer.py, change the
dependnecy to an optional extra.
[1]: https://github.com/zephyrproject-rtos/windows-curses/issues/31
* change python3 to python for Windows
Co-authored-by: Hashem Nasarat
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
doc/installation.rst | 10 ++++++++++
setup.py | 4 +++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/doc/installation.rst b/doc/installation.rst
index fe7204ba6..bfce72180 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -103,6 +103,16 @@ If ``python-can`` is already installed, the CANtact backend can be installed sep
Additional CANtact documentation is available at `cantact.io `__.
+CanViewer
+~~~~~~~~~
+
+``python-can`` has support for showing a simple CAN viewer terminal application
+by running ``python -m can.viewer``. On Windows, this depends on the
+`windows-curses library `__ which can
+be installed with:
+
+``python -m pip install "python-can[viewer]"``
+
Installing python-can in development mode
-----------------------------------------
diff --git a/setup.py b/setup.py
index c9defecab..adbd61f91 100644
--- a/setup.py
+++ b/setup.py
@@ -33,6 +33,9 @@
"gs_usb": ["gs_usb>=0.2.1"],
"nixnet": ["nixnet>=0.3.1"],
"pcan": ["uptime~=3.0.1"],
+ "viewer": [
+ 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"'
+ ],
}
setup(
@@ -86,7 +89,6 @@
install_requires=[
"setuptools",
"wrapt~=1.10",
- 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"',
"typing_extensions>=3.10.0.0",
'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"',
'msgpack~=1.0.0;platform_system!="Windows"',
From a144f25689728bad7ed39de4a70f2260c04df9be Mon Sep 17 00:00:00 2001
From: Martin Kletzander
Date: Fri, 7 Oct 2022 20:32:47 +0200
Subject: [PATCH 144/475] Test load_config() (#1396)
* listener_test: Do not modify environment for other tests
* network_test: Do not modify environment for other tests
Also do not skip the test since there will always be an interface (previously
set globally and now prepared in setUp function.
* test_load_file_config: Test with different values
Testing with the same interface values would not show possible issues in case
there were any.
* tests: Add test for load_config
Add tests for load_config with contexts and environment variables.
Closes #345
Signed-off-by: Martin Kletzander
Signed-off-by: Martin Kletzander
---
test/listener_test.py | 8 ++--
test/network_test.py | 11 ++++-
test/test_load_config.py | 86 +++++++++++++++++++++++++++++++++++
test/test_load_file_config.py | 2 +-
4 files changed, 101 insertions(+), 6 deletions(-)
create mode 100644 test/test_load_config.py
diff --git a/test/listener_test.py b/test/listener_test.py
index e5abd94a2..0e64a266a 100644
--- a/test/listener_test.py
+++ b/test/listener_test.py
@@ -15,9 +15,6 @@
from .data.example_data import generate_message
-channel = "virtual_channel_0"
-can.rc["interface"] = "virtual"
-
logging.basicConfig(level=logging.DEBUG)
# makes the random number generator deterministic
@@ -55,10 +52,15 @@ def testClassesImportable(self):
class BusTest(unittest.TestCase):
def setUp(self):
+ # Save all can.rc defaults
+ self._can_rc = can.rc
+ can.rc = {"interface": "virtual"}
self.bus = can.interface.Bus()
def tearDown(self):
self.bus.shutdown()
+ # Restore the defaults
+ can.rc = self._can_rc
class ListenerTest(BusTest):
diff --git a/test/network_test.py b/test/network_test.py
index 5900cd10f..58c305a38 100644
--- a/test/network_test.py
+++ b/test/network_test.py
@@ -14,10 +14,8 @@
import can
channel = "vcan0"
-can.rc["interface"] = "virtual"
-@unittest.skipIf("interface" not in can.rc, "Need a CAN interface")
class ControllerAreaNetworkTestCase(unittest.TestCase):
"""
This test ensures that what messages go in to the bus is what comes out.
@@ -42,6 +40,15 @@ class ControllerAreaNetworkTestCase(unittest.TestCase):
for b in range(num_messages)
)
+ def setUp(self):
+ # Save all can.rc defaults
+ self._can_rc = can.rc
+ can.rc = {"interface": "virtual"}
+
+ def tearDown(self):
+ # Restore the defaults
+ can.rc = self._can_rc
+
def producer(self, ready_event, msg_read):
self.client_bus = can.interface.Bus(channel=channel)
ready_event.wait()
diff --git a/test/test_load_config.py b/test/test_load_config.py
new file mode 100644
index 000000000..a2969b0a5
--- /dev/null
+++ b/test/test_load_config.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+
+import os
+import shutil
+import tempfile
+import unittest
+from tempfile import NamedTemporaryFile
+
+import can
+
+
+class LoadConfigTest(unittest.TestCase):
+ configuration = {
+ "default": {"interface": "serial", "channel": "0"},
+ "one": {"interface": "kvaser", "channel": "1", "bitrate": 100000},
+ "two": {"channel": "2"},
+ }
+
+ def setUp(self):
+ # Create a temporary directory
+ self.test_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ # Remove the directory after the test
+ shutil.rmtree(self.test_dir)
+
+ def _gen_configration_file(self, sections):
+ with NamedTemporaryFile(
+ mode="w", dir=self.test_dir, delete=False
+ ) as tmp_config_file:
+ content = []
+ for section in sections:
+ content.append("[{}]".format(section))
+ for k, v in self.configuration[section].items():
+ content.append("{} = {}".format(k, v))
+ tmp_config_file.write("\n".join(content))
+ return tmp_config_file.name
+
+ def _dict_to_env(self, d):
+ return {f"CAN_{k.upper()}": str(v) for k, v in d.items()}
+
+ def test_config_default(self):
+ tmp_config = self._gen_configration_file(["default"])
+ config = can.util.load_config(path=tmp_config)
+ self.assertEqual(config, self.configuration["default"])
+
+ def test_config_whole_default(self):
+ tmp_config = self._gen_configration_file(self.configuration)
+ config = can.util.load_config(path=tmp_config)
+ self.assertEqual(config, self.configuration["default"])
+
+ def test_config_whole_context(self):
+ tmp_config = self._gen_configration_file(self.configuration)
+ config = can.util.load_config(path=tmp_config, context="one")
+ self.assertEqual(config, self.configuration["one"])
+
+ def test_config_merge_context(self):
+ tmp_config = self._gen_configration_file(self.configuration)
+ config = can.util.load_config(path=tmp_config, context="two")
+ expected = self.configuration["default"]
+ expected.update(self.configuration["two"])
+ self.assertEqual(config, expected)
+
+ def test_config_merge_environment_to_context(self):
+ tmp_config = self._gen_configration_file(self.configuration)
+ env_data = {"interface": "serial", "bitrate": 125000}
+ env_dict = self._dict_to_env(env_data)
+ with unittest.mock.patch.dict("os.environ", env_dict):
+ config = can.util.load_config(path=tmp_config, context="one")
+ expected = self.configuration["one"]
+ expected.update(env_data)
+ self.assertEqual(config, expected)
+
+ def test_config_whole_environment(self):
+ tmp_config = self._gen_configration_file(self.configuration)
+ env_data = {"interface": "socketcan", "channel": "3", "bitrate": 250000}
+ env_dict = self._dict_to_env(env_data)
+ with unittest.mock.patch.dict("os.environ", env_dict):
+ config = can.util.load_config(path=tmp_config, context="one")
+ expected = self.configuration["one"]
+ expected.update(env_data)
+ self.assertEqual(config, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py
index c71e6ccd6..6b1d5a382 100644
--- a/test/test_load_file_config.py
+++ b/test/test_load_file_config.py
@@ -11,7 +11,7 @@
class LoadFileConfigTest(unittest.TestCase):
configuration = {
"default": {"interface": "virtual", "channel": "0"},
- "one": {"interface": "virtual", "channel": "1"},
+ "one": {"interface": "kvaser", "channel": "1"},
"two": {"channel": "2"},
"three": {"extra": "extra value"},
}
From b639560594d9dbb570efd67d64877b743fdf9aef Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Fri, 7 Oct 2022 16:20:31 -0500
Subject: [PATCH 145/475] Modify `file_size` help doc string (#1401)
* Modify `file_size` help doc string
* Add note to BLFWriter
* Revert "Add note to BLFWriter"
This reverts commit 18241acc527b82ee81aa0e546410a158b18ad1d1.
* Add in a note about the nuanced file size in help statement
based on input from @zariiii9003
* Modify help statement to clarify consistency
* Minor tweak
* Control BLFWriter max container size
* Fix formatting
* Update can/logger.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Revert "Fix formatting"
This reverts commit 3afb140a09c4c0a5394f5a89d7e0d49397c4a37d.
* Revert "Control BLFWriter max container size"
This reverts commit c39719ccf88ef9c6eae8facffcac063a10f519c5.
Co-authored-by: j-c-cook
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/logger.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/can/logger.py b/can/logger.py
index 0b73dd785..8cb201987 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -193,9 +193,10 @@ def main() -> None:
"--file_size",
dest="file_size",
type=int,
- help="Maximum file size in bytes (or for the case of blf, maximum "
- "buffer size before compression and flush to file). Rotate log "
- "file when size threshold is reached.",
+ help="Maximum file size in bytes. Rotate log file when size threshold "
+ "is reached. (The resulting file sizes will be consistent, but are not "
+ "guaranteed to be exactly what is specified here due to the rollover "
+ "conditions being logger implementation specific.)",
default=None,
)
From 451ce48eacd529453c14ec59fe6bc5d142f2e65a Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 9 Oct 2022 23:08:36 +0200
Subject: [PATCH 146/475] Fix Sphinx warnings (#1405)
* fix most sphinx warnings
* build docs in CI
* install package
* fix extlink caption string deprecation warning
* add Seeed Studio to title
* update readthedocs configuration
* add mocks for windows specifics
* mypy should ignore conf.py
* upload sphinx artifact
* set retention to 5 days
* set type annotation location
---
.github/workflows/build.yml | 22 +++++++++++
.readthedocs.yml | 31 +++++++++++++++
can/broadcastmanager.py | 2 +-
can/bus.py | 31 +++++++++------
can/exceptions.py | 2 +-
can/interfaces/ixxat/canlib_vcinpl.py | 6 +--
can/interfaces/ixxat/canlib_vcinpl2.py | 5 +--
can/interfaces/kvaser/canlib.py | 3 +-
can/interfaces/kvaser/structures.py | 7 +---
can/interfaces/nican.py | 10 ++---
can/interfaces/pcan/pcan.py | 4 +-
can/interfaces/robotell.py | 8 ++--
can/interfaces/serial/serial_can.py | 10 +++--
can/interfaces/slcan.py | 4 +-
can/interfaces/socketcan/socketcan.py | 13 ++++---
can/interfaces/systec/ucanbus.py | 8 ++--
can/interfaces/usb2can/usb2canInterface.py | 21 +++++-----
.../usb2can/usb2canabstractionlayer.py | 22 +++++++----
can/interfaces/vector/canlib.py | 6 +--
can/interfaces/virtual.py | 4 +-
can/io/logger.py | 39 ++++++++++---------
can/io/sqlite.py | 2 +-
can/message.py | 2 +-
can/typechecking.py | 4 +-
can/util.py | 9 +++--
doc/bcm.rst | 4 ++
doc/bus.rst | 11 +++++-
doc/conf.py | 29 ++++++++++++--
doc/doc-requirements.txt | 4 +-
doc/interfaces.rst | 1 +
doc/interfaces/etas.rst | 4 +-
doc/interfaces/ixxat.rst | 19 ++++++++-
doc/interfaces/kvaser.rst | 2 +
doc/interfaces/neovi.rst | 5 ++-
doc/interfaces/nican.rst | 1 +
doc/interfaces/pcan.rst | 1 +
doc/interfaces/seeedstudio.rst | 5 +--
doc/interfaces/serial.rst | 2 +
doc/interfaces/socketcan.rst | 6 ++-
doc/interfaces/socketcand.rst | 13 ++++---
doc/interfaces/usb2can.rst | 2 +
doc/interfaces/vector.rst | 6 +--
doc/interfaces/virtual.rst | 6 ++-
doc/internal-api.rst | 2 +-
doc/message.rst | 2 +
doc/scripts.rst | 2 +-
setup.cfg | 1 +
47 files changed, 267 insertions(+), 136 deletions(-)
create mode 100644 .readthedocs.yml
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5365620cd..aad9274fe 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -87,3 +87,25 @@ jobs:
- name: Code Format Check with Black
run: |
black --check --verbose .
+
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v3
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -e .[canalystii,gs_usb]
+ pip install -r doc/doc-requirements.txt
+ - name: Build documentation
+ run: |
+ python -m sphinx -an doc build
+ - uses: actions/upload-artifact@v3
+ with:
+ name: sphinx-out
+ path: ./build/
+ retention-days: 5
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 000000000..74cb9dbdd
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,31 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.10"
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: doc/conf.py
+
+# If using Sphinx, optionally build your docs in additional formats such as PDF
+formats:
+ - pdf
+ - epub
+
+# Optionally declare the Python requirements required to build your docs
+python:
+ install:
+ - requirements: doc/doc-requirements.txt
+ - method: pip
+ path: .
+ extra_requirements:
+ - canalystii
+ - gs_usb
diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py
index c15186e2c..239d1d7d5 100644
--- a/can/broadcastmanager.py
+++ b/can/broadcastmanager.py
@@ -39,7 +39,7 @@ class CyclicTask(abc.ABC):
def stop(self) -> None:
"""Cancel this periodic task.
- :raises can.CanError:
+ :raises ~can.exceptions.CanError:
If stop is called on an already stopped task.
"""
diff --git a/can/bus.py b/can/bus.py
index b9ccfcfad..c0793c5f7 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -66,8 +66,10 @@ def __init__(
Any backend dependent configurations are passed in this dictionary
:raises ValueError: If parameters are out of range
- :raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed
- :raises can.CanInitializationError: If the bus cannot be initialized
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
+ If the driver cannot be accessed
+ :raises ~can.exceptions.CanInitializationError:
+ If the bus cannot be initialized
"""
self._periodic_tasks: List[_SelfRemovingCyclicTask] = []
self.set_filters(can_filters)
@@ -81,9 +83,11 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
:param timeout:
seconds to wait for a message or None to wait indefinitely
- :return: ``None`` on timeout or a :class:`Message` object.
+ :return:
+ :obj:`None` on timeout or a :class:`~can.Message` object.
- :raises can.CanOperationError: If an error occurred while reading
+ :raises ~can.exceptions.CanOperationError:
+ If an error occurred while reading
"""
start = time()
time_left = timeout
@@ -148,7 +152,8 @@ def _recv_internal(
2. a bool that is True if message filtering has already
been done and else False
- :raises can.CanOperationError: If an error occurred while reading
+ :raises ~can.exceptions.CanOperationError:
+ If an error occurred while reading
:raises NotImplementedError:
if the bus provides it's own :meth:`~can.BusABC.recv`
implementation (legacy implementation)
@@ -171,7 +176,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
Might not be supported by all interfaces.
None blocks indefinitely.
- :raises can.CanOperationError: If an error occurred while sending
+ :raises ~can.exceptions.CanOperationError:
+ If an error occurred while sending
"""
raise NotImplementedError("Trying to write to a readonly bus?")
@@ -189,8 +195,8 @@ def send_periodic(
- the (optional) duration expires
- the Bus instance goes out of scope
- the Bus instance is shutdown
- - :meth:`BusABC.stop_all_periodic_tasks()` is called
- - the task's :meth:`CyclicTask.stop()` method is called.
+ - :meth:`stop_all_periodic_tasks` is called
+ - the task's :meth:`~can.broadcastmanager.CyclicTask.stop` method is called.
:param msgs:
Message(s) to transmit
@@ -204,7 +210,8 @@ def send_periodic(
Disable to instead manage tasks manually.
:return:
A started task instance. Note the task can be stopped (and depending on
- the backend modified) by calling the task's :meth:`stop` method.
+ the backend modified) by calling the task's
+ :meth:`~can.broadcastmanager.CyclicTask.stop` method.
.. note::
@@ -274,8 +281,8 @@ def _send_periodic_internal(
no duration is provided, the task will continue indefinitely.
:return:
A started task instance. Note the task can be stopped (and
- depending on the backend modified) by calling the :meth:`stop`
- method.
+ depending on the backend modified) by calling the
+ :meth:`~can.broadcastmanager.CyclicTask.stop` method.
"""
if not hasattr(self, "_lock_send_periodic"):
# Create a send lock for this bus, but not for buses which override this method
@@ -288,7 +295,7 @@ def _send_periodic_internal(
return task
def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None:
- """Stop sending any messages that were started using **bus.send_periodic**.
+ """Stop sending any messages that were started using :meth:`send_periodic`.
.. note::
The result is undefined if a single task throws an exception while being stopped.
diff --git a/can/exceptions.py b/can/exceptions.py
index 5a7aa0b7c..dc08be3b8 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -74,7 +74,7 @@ class CanInitializationError(CanError):
"""Indicates an error the occurred while initializing a :class:`can.BusABC`.
If initialization fails due to a driver or platform missing/being unsupported,
- a :class:`can.CanInterfaceNotImplementedError` is raised instead.
+ a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead.
If initialization fails due to a value being out of range, a :class:`ValueError`
is raised.
diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py
index cb0447b49..bdb05cda5 100644
--- a/can/interfaces/ixxat/canlib_vcinpl.py
+++ b/can/interfaces/ixxat/canlib_vcinpl.py
@@ -372,10 +372,8 @@ class IXXATBus(BusABC):
.. 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.
-
+ the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
+ Using :meth:`~can.BusABC.set_filters` does not work.
"""
CHANNEL_BITRATES = {
diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py
index 37085d74a..802168630 100644
--- a/can/interfaces/ixxat/canlib_vcinpl2.py
+++ b/can/interfaces/ixxat/canlib_vcinpl2.py
@@ -411,9 +411,8 @@ class IXXATBus(BusABC):
.. 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.
+ the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
+ Using :meth:`~can.BusABC.set_filters` does not work.
"""
diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py
index a951e39de..f60a43bc5 100644
--- a/can/interfaces/kvaser/canlib.py
+++ b/can/interfaces/kvaser/canlib.py
@@ -657,7 +657,7 @@ def shutdown(self):
canBusOff(self._write_handle)
canClose(self._write_handle)
- def get_stats(self):
+ def get_stats(self) -> structures.BusStatistics:
"""Retrieves the bus statistics.
Use like so:
@@ -667,7 +667,6 @@ def get_stats(self):
std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0
:returns: bus statistics.
- :rtype: can.interfaces.kvaser.structures.BusStatistics
"""
canRequestBusStatistics(self._write_handle)
stats = structures.BusStatistics()
diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py
index c7d363dd4..996f16c37 100644
--- a/can/interfaces/kvaser/structures.py
+++ b/can/interfaces/kvaser/structures.py
@@ -7,11 +7,8 @@
class BusStatistics(ctypes.Structure):
- """
- This structure is used with the method :meth:`KvaserBus.get_stats`.
-
- .. seealso:: :meth:`KvaserBus.get_stats`
-
+ """This structure is used with the method
+ :meth:`~can.interfaces.kvaser.canlib.KvaserBus.get_stats`.
"""
_fields_ = [
diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py
index 0beeee429..ea13e28e8 100644
--- a/can/interfaces/nican.py
+++ b/can/interfaces/nican.py
@@ -179,10 +179,8 @@ class NicanBus(BusABC):
.. 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.
-
+ the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
+ Using :meth:`~can.BusABC.set_filters` does not work.
"""
def __init__(
@@ -208,9 +206,9 @@ def __init__(
``is_error_frame`` set to True and ``arbitration_id`` will identify
the error (default True)
- :raise can.CanInterfaceNotImplementedError:
+ :raise ~can.exceptions.CanInterfaceNotImplementedError:
If the current operating system is not supported or the driver could not be loaded.
- :raise can.interfaces.nican.NicanInitializationError:
+ :raise ~can.interfaces.nican.NicanInitializationError:
If the bus could not be set up.
"""
if nican is None:
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index e5b877762..c60b9e6c9 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -113,8 +113,8 @@ def __init__(
"""A PCAN USB interface to CAN.
On top of the usual :class:`~can.Bus` methods provided,
- the PCAN interface includes the :meth:`~can.interface.pcan.PcanBus.flash`
- and :meth:`~can.interface.pcan.PcanBus.status` methods.
+ the PCAN interface includes the :meth:`flash`
+ and :meth:`status` methods.
:param str channel:
The can interface name. An example would be 'PCAN_USBBUS1'.
diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py
index 709fad78d..0d3ad1b77 100644
--- a/can/interfaces/robotell.py
+++ b/can/interfaces/robotell.py
@@ -5,9 +5,10 @@
import io
import time
import logging
+from typing import Optional
from can import BusABC, Message
-from ..exceptions import CanInterfaceNotImplementedError
+from ..exceptions import CanInterfaceNotImplementedError, CanOperationError
logger = logging.getLogger(__name__)
@@ -377,12 +378,11 @@ def fileno(self):
except Exception as exception:
raise CanOperationError("Cannot fetch fileno") from exception
- def get_serial_number(self, timeout):
+ def get_serial_number(self, timeout: Optional[int]) -> Optional[str]:
"""Get serial number of the slcan interface.
- :type timeout: int or None
+
:param timeout:
seconds to wait for serial number or None to wait indefinitely
- :rtype str or None
:return:
None on timeout or a str object.
"""
diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py
index ec4bb8671..c1507b4fa 100644
--- a/can/interfaces/serial/serial_can.py
+++ b/can/interfaces/serial/serial_can.py
@@ -74,8 +74,10 @@ def __init__(
:param rtscts:
turn hardware handshake (RTS/CTS) on and off
- :raises can.CanInitializationError: If the given parameters are invalid.
- :raises can.CanInterfaceNotImplementedError: If the serial module is not installed.
+ :raises ~can.exceptions.CanInitializationError:
+ If the given parameters are invalid.
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
+ If the serial module is not installed.
"""
if not serial:
@@ -163,10 +165,10 @@ def _recv_internal(
This parameter will be ignored. The timeout value of the channel is used.
:returns:
- Received message and `False` (because no filtering as taken place).
+ Received message and :obj:`False` (because no filtering as taken place).
.. warning::
- Flags like is_extended_id, is_remote_frame and is_error_frame
+ Flags like ``is_extended_id``, ``is_remote_frame`` and ``is_error_frame``
will not be set over this function, the flags in the return
message are the default values.
"""
diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py
index 63ea4ca42..212c4c85c 100644
--- a/can/interfaces/slcan.py
+++ b/can/interfaces/slcan.py
@@ -323,10 +323,10 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
"""Get serial number of the slcan interface.
:param timeout:
- seconds to wait for serial number or ``None`` to wait indefinitely
+ seconds to wait for serial number or :obj:`None` to wait indefinitely
:return:
- ``None`` on timeout or a :class:`~builtin.str` object.
+ :obj:`None` on timeout or a :class:`str` object.
"""
cmd = "N"
self._write(cmd)
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index c7c038520..549998dc8 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -402,7 +402,7 @@ def stop(self) -> None:
"""Stop a task by sending TX_DELETE message to Linux kernel.
This will delete the entry for the transmission of the CAN-message
- with the specified :attr:`~task_id` identifier. The message length
+ with the specified ``task_id`` identifier. The message length
for the command TX_DELETE is {[bcm_msg_head]} (only the header).
"""
log.debug("Stopping periodic task")
@@ -444,7 +444,7 @@ def start(self) -> None:
message to Linux kernel prior to scheduling.
:raises ValueError:
- If the task referenced by :attr:`~task_id` is already running.
+ If the task referenced by ``task_id`` is already running.
"""
self._tx_setup(self.messages)
@@ -617,9 +617,10 @@ def __init__(
If setting some socket options fails, an error will be printed but no exception will be thrown.
This includes enabling:
- - that own messages should be received,
- - CAN-FD frames and
- - error frames.
+
+ - that own messages should be received,
+ - CAN-FD frames and
+ - error frames.
:param channel:
The can interface name with which to create this bus.
@@ -739,7 +740,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
Wait up to this many seconds for the transmit queue to be ready.
If not given, the call may fail immediately.
- :raises can.CanError:
+ :raises ~can.exceptions.CanError:
if the message could not be written.
"""
log.debug("We've been asked to write a message to the bus")
diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py
index 88224b856..fee110b08 100644
--- a/can/interfaces/systec/ucanbus.py
+++ b/can/interfaces/systec/ucanbus.py
@@ -88,10 +88,10 @@ def __init__(self, channel, can_filters=None, **kwargs):
:raises ValueError:
If invalid input parameter were passed.
- :raises can.CanInterfaceNotImplementedError:
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
If the platform is not supported.
- :raises can.CanInitializationError:
+ :raises ~can.exceptions.CanInitializationError:
If hardware or CAN interface initialization failed.
"""
try:
@@ -181,7 +181,7 @@ def send(self, msg, timeout=None):
:param float timeout:
Transmit timeout in seconds (value 0 switches off the "auto delete")
- :raises can.CanOperationError:
+ :raises ~can.exceptions.CanOperationError:
If the message could not be sent.
"""
try:
@@ -243,7 +243,7 @@ def flush_tx_buffer(self):
"""
Flushes the transmit buffer.
- :raises can.CanError:
+ :raises ~can.exceptions.CanError:
If flushing of the transmit buffer failed.
"""
log.info("Flushing transmit buffer")
diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py
index e51d485cd..504b61c7b 100644
--- a/can/interfaces/usb2can/usb2canInterface.py
+++ b/can/interfaces/usb2can/usb2canInterface.py
@@ -67,22 +67,22 @@ class Usb2canBus(BusABC):
This interface only works on Windows.
Please use socketcan on Linux.
- :param str channel (optional):
+ :param channel:
The device's serial number. If not provided, Windows Management Instrumentation
will be used to identify the first such device.
- :param int bitrate (optional):
+ :param bitrate:
Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s.
Default is 500 Kbs
- :param int flags (optional):
+ :param flags:
Flags to directly pass to open function of the usb2can abstraction layer.
- :param str dll (optional):
+ :param dll:
Path to the DLL with the CANAL API to load
Defaults to 'usb2can.dll'
- :param str serial (optional):
+ :param serial:
Alias for `channel` that is provided for legacy reasons.
If both `serial` and `channel` are set, `serial` will be used and
channel will be ignored.
@@ -91,18 +91,19 @@ class Usb2canBus(BusABC):
def __init__(
self,
- channel=None,
- dll="usb2can.dll",
- flags=0x00000008,
+ channel: Optional[str] = None,
+ dll: str = "usb2can.dll",
+ flags: int = 0x00000008,
*_,
- bitrate=500000,
+ bitrate: int = 500000,
+ serial: Optional[str] = None,
**kwargs,
):
self.can = Usb2CanAbstractionLayer(dll)
# get the serial number of the device
- device_id = kwargs.get("serial", channel)
+ device_id = serial or channel
# search for a serial number if the device_id is None or empty
if not device_id:
diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py
index 8a3ae34ca..a6708cb42 100644
--- a/can/interfaces/usb2can/usb2canabstractionlayer.py
+++ b/can/interfaces/usb2can/usb2canabstractionlayer.py
@@ -9,6 +9,7 @@
import can
from ...exceptions import error_check
+from ...typechecking import StringPathLike
log = logging.getLogger("can.usb2can")
@@ -108,12 +109,13 @@ class Usb2CanAbstractionLayer:
Documentation: http://www.8devices.com/media/products/usb2can/downloads/CANAL_API.pdf
"""
- def __init__(self, dll="usb2can.dll"):
+ def __init__(self, dll: StringPathLike = "usb2can.dll") -> None:
"""
- :type dll: str or path-like
- :param dll (optional): the path to the usb2can DLL to load
+ :param dll:
+ the path to the usb2can DLL to load
- :raises can.CanInterfaceNotImplementedError: if the DLL could not be loaded
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
+ if the DLL could not be loaded
"""
try:
self.__m_dllBasic = windll.LoadLibrary(dll)
@@ -128,11 +130,15 @@ def open(self, configuration: str, flags: int):
"""
Opens a CAN connection using `CanalOpen()`.
- :param configuration: the configuration: "device_id; baudrate"
- :param flags: the flags to be set
+ :param configuration:
+ the configuration: "device_id; baudrate"
+ :param flags:
+ the flags to be set
+ :returns:
+ Valid handle for CANAL API functions on success
- :raises can.CanInitializationError: if any error occurred
- :returns: Valid handle for CANAL API functions on success
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
+ if any error occurred
"""
try:
# we need to convert this into bytes, since the underlying DLL cannot
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 7737a99d3..a36f67e9e 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -138,11 +138,11 @@ def __init__(
:param tseg2_dbr:
Bus timing value tseg2 (data)
- :raise can.CanInterfaceNotImplementedError:
+ :raise ~can.exceptions.CanInterfaceNotImplementedError:
If the current operating system is not supported or the driver could not be loaded.
- :raise can.CanInitializationError:
+ :raise can.exceptions.CanInitializationError:
If the bus could not be set up.
- This may or may not be a :class:`can.interfaces.vector.VectorInitializationError`.
+ This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`.
"""
if os.name != "nt" and not kwargs.get("_testing", False):
raise CanInterfaceNotImplementedError(
diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py
index ffd5b0241..cc71469b5 100644
--- a/can/interfaces/virtual.py
+++ b/can/interfaces/virtual.py
@@ -40,7 +40,7 @@ class VirtualBus(BusABC):
an identifier for connected buses.
Implements :meth:`can.BusABC._detect_available_configs`; see
- :meth:`can.VirtualBus._detect_available_configs` for how it
+ :meth:`_detect_available_configs` for how it
behaves here.
.. note::
@@ -84,7 +84,7 @@ def __init__(
self.channel.append(self.queue)
def _check_if_open(self) -> None:
- """Raises :class:`~can.CanOperationError` if the bus is not open.
+ """Raises :exc:`~can.exceptions.CanOperationError` if the bus is not open.
Has to be called in every method that accesses the bus.
"""
diff --git a/can/io/logger.py b/can/io/logger.py
index ec34079b4..df13c6256 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -120,32 +120,31 @@ def on_message_received(self, msg: Message) -> None:
class BaseRotatingLogger(Listener, BaseIOHandler, ABC):
"""
Base class for rotating CAN loggers. This class is not meant to be
- instantiated directly. Subclasses must implement the :attr:`should_rollover`
- and `do_rollover` methods according to their rotation strategy.
+ instantiated directly. Subclasses must implement the :meth:`should_rollover`
+ and :meth:`do_rollover` methods according to their rotation strategy.
The rotation behavior can be further customized by the user by setting
the :attr:`namer` and :attr:`rotator` attributes after instantiating the subclass.
- These attributes as well as the methods `rotation_filename` and `rotate`
+ These attributes as well as the methods :meth:`rotation_filename` and :meth:`rotate`
and the corresponding docstrings are carried over from the python builtin
- `BaseRotatingHandler`.
+ :class:`~logging.handlers.BaseRotatingHandler`.
Subclasses must set the `_writer` attribute upon initialization.
- :attr namer:
- If this attribute is set to a callable, the :meth:`rotation_filename` method
- delegates to this callable. The parameters passed to the callable are
- those passed to :meth:`rotation_filename`.
- :attr rotator:
- If this attribute is set to a callable, the :meth:`rotate` method delegates
- to this callable. The parameters passed to the callable are those
- passed to :meth:`rotate`.
- :attr rollover_count:
- An integer counter to track the number of rollovers.
"""
+ #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename`
+ #: method delegates to this callable. The parameters passed to the callable are
+ #: those passed to :meth:`~BaseRotatingLogger.rotation_filename`.
namer: Optional[Callable[[StringPathLike], StringPathLike]] = None
+
+ #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotate` method
+ #: delegates to this callable. The parameters passed to the callable are those
+ #: passed to :meth:`~BaseRotatingLogger.rotate`.
rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None
+
+ #: An integer counter to track the number of rollovers.
rollover_count: int = 0
def __init__(self, *args: Any, **kwargs: Any) -> None:
@@ -169,7 +168,7 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike:
This is provided so that a custom filename can be provided.
The default implementation calls the :attr:`namer` attribute of the
handler, if it's callable, passing the default name to
- it. If the attribute isn't callable (the default is `None`), the name
+ it. If the attribute isn't callable (the default is :obj:`None`), the name
is returned unchanged.
:param default_name:
@@ -184,8 +183,8 @@ def rotate(self, source: StringPathLike, dest: StringPathLike) -> None:
"""When rotating, rotate the current log.
The default implementation calls the :attr:`rotator` attribute of the
- handler, if it's callable, passing the source and dest arguments to
- it. If the attribute isn't callable (the default is `None`), the source
+ handler, if it's callable, passing the `source` and `dest` arguments to
+ it. If the attribute isn't callable (the default is :obj:`None`), the source
is simply renamed to the destination.
:param source:
@@ -273,8 +272,10 @@ class SizedRotatingLogger(BaseRotatingLogger):
by adding a timestamp and the rollover count. A new log file is then
created and written to.
- This behavior can be customized by setting the :attr:`namer` and
- :attr:`rotator` attribute.
+ This behavior can be customized by setting the
+ :attr:`~can.io.BaseRotatingLogger.namer` and
+ :attr:`~can.io.BaseRotatingLogger.rotator`
+ attribute.
Example::
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index b9cbf9f93..0a4de85f2 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -25,7 +25,7 @@ class SqliteReader(MessageReader):
This class can be iterated over or used to fetch all messages in the
database with :meth:`~SqliteReader.read_all`.
- Calling :func:`~builtin.len` on this object might not run in constant time.
+ Calling :func:`len` on this object might not run in constant time.
:attr str table_name: the name of the database table used for storing the messages
diff --git a/can/message.py b/can/message.py
index 87cb6a199..8e0c4deee 100644
--- a/can/message.py
+++ b/can/message.py
@@ -29,7 +29,7 @@ class Message: # pylint: disable=too-many-instance-attributes; OK for a datacla
:func:`~copy.copy`/:func:`~copy.deepcopy` is supported as well.
Messages do not support "dynamic" attributes, meaning any others than the
- documented ones, since it uses :attr:`~object.__slots__`.
+ documented ones, since it uses :obj:`~object.__slots__`.
"""
__slots__ = (
diff --git a/can/typechecking.py b/can/typechecking.py
index ed76b6c85..b3a513a3a 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -8,7 +8,9 @@
import typing_extensions
-CanFilter = typing_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int})
+CanFilter: typing_extensions = typing_extensions.TypedDict(
+ "CanFilter", {"can_id": int, "can_mask": int}
+)
CanFilterExtended = typing_extensions.TypedDict(
"CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool}
)
diff --git a/can/util.py b/can/util.py
index d1ff643de..e64eb13b5 100644
--- a/can/util.py
+++ b/can/util.py
@@ -258,8 +258,10 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
def set_logging_level(level_name: str) -> None:
"""Set the logging level for the `"can"` logger.
- :param level_name: One of: `'critical'`, `'error'`, `'warning'`, `'info'`,
- `'debug'`, `'subdebug'`, or the value `None` (=default). Defaults to `'debug'`.
+ :param level_name:
+ One of: `'critical'`, `'error'`, `'warning'`, `'info'`,
+ `'debug'`, `'subdebug'`, or the value :obj:`None` (=default).
+ Defaults to `'debug'`.
"""
can_logger = logging.getLogger("can")
@@ -316,7 +318,7 @@ def deprecated_args_alias(**aliases):
"""Allows to rename/deprecate a function kwarg(s) and optionally
have the deprecated kwarg(s) set as alias(es)
- Example:
+ Example::
@deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg")
def library_function(new_arg, another_new_arg):
@@ -325,6 +327,7 @@ def library_function(new_arg, another_new_arg):
@deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None)
def library_function(new_arg):
pass
+
"""
def deco(f):
diff --git a/doc/bcm.rst b/doc/bcm.rst
index 549b06edd..94cde0e60 100644
--- a/doc/bcm.rst
+++ b/doc/bcm.rst
@@ -42,3 +42,7 @@ which inherits from :class:`~can.broadcastmanager.CyclicTask`.
.. autoclass:: can.RestartableCyclicTaskABC
:members:
+
+.. autoclass:: can.broadcastmanager.ThreadBasedCyclicSendTask
+ :members:
+
diff --git a/doc/bus.rst b/doc/bus.rst
index bbe52cbd6..9f7077cc1 100644
--- a/doc/bus.rst
+++ b/doc/bus.rst
@@ -20,7 +20,6 @@ Autoconfig Bus
.. autoclass:: can.Bus
:members:
- :undoc-members:
API
@@ -28,9 +27,17 @@ API
.. autoclass:: can.BusABC
:members:
- :undoc-members:
.. automethod:: __iter__
+ .. automethod:: _recv_internal
+ .. automethod:: _apply_filters
+ .. automethod:: _detect_available_configs
+ .. automethod:: _send_periodic_internal
+
+.. autoclass:: can.bus.BusState
+ :members:
+ :undoc-members:
+
Transmitting
''''''''''''
diff --git a/doc/conf.py b/doc/conf.py
index 14390d5ad..495416078 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -8,6 +8,8 @@
import sys
import os
+import ctypes
+from unittest.mock import MagicMock
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -15,6 +17,7 @@
sys.path.insert(0, os.path.abspath(".."))
import can # pylint: disable=wrong-import-position
+from can import ctypesutil
# -- General configuration -----------------------------------------------------
@@ -45,11 +48,11 @@
"sphinx.ext.viewcode",
"sphinx.ext.graphviz",
"sphinxcontrib.programoutput",
- "sphinx_autodoc_typehints",
+ "sphinx_rtd_theme",
]
# Now, you can use the alias name as a new role, e.g. :issue:`123`.
-extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue ")}
+extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue #%s")}
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
@@ -111,11 +114,31 @@
# Keep cached intersphinx inventories indefinitely
intersphinx_cache_limit = -1
+# location of typehints
+autodoc_typehints = "description"
+
+# disable specific warnings
+nitpick_ignore = [
+ # Ignore warnings for type aliases. Remove once Sphinx supports PEP613
+ ("py:class", "can.typechecking.BusConfig"),
+ ("py:class", "can.typechecking.CanFilter"),
+ ("py:class", "can.typechecking.CanFilterExtended"),
+ ("py:class", "can.typechecking.AutoDetectedConfig"),
+ # intersphinx fails to reference some builtins
+ ("py:class", "asyncio.events.AbstractEventLoop"),
+ ("py:class", "_thread.allocate_lock"),
+]
+
+# mock windows specific attributes
+autodoc_mock_imports = ["win32com"]
+ctypes.windll = MagicMock()
+ctypesutil.HRESULT = ctypes.c_long
+
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = "default"
+html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt
index dead5e2e5..9732ea4ef 100644
--- a/doc/doc-requirements.txt
+++ b/doc/doc-requirements.txt
@@ -1,3 +1,3 @@
-sphinx>=1.8.1
+sphinx>=5.2.3
sphinxcontrib-programoutput
-sphinx-autodoc-typehints
+sphinx_rtd_theme
diff --git a/doc/interfaces.rst b/doc/interfaces.rst
index 757cf67b4..dbe4ad426 100644
--- a/doc/interfaces.rst
+++ b/doc/interfaces.rst
@@ -26,6 +26,7 @@ The available interfaces are:
interfaces/serial
interfaces/slcan
interfaces/socketcan
+ interfaces/socketcand
interfaces/systec
interfaces/udp_multicast
interfaces/usb2can
diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst
index cc3cbdea4..2b59a4eee 100644
--- a/doc/interfaces/etas.rst
+++ b/doc/interfaces/etas.rst
@@ -6,7 +6,7 @@ The ETAS BOA_ (Basic Open API) is used.
Install the "ETAS ECU and Bus Interfaces – Distribution Package".
Only Windows is supported by this interface.
The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules.
-To use these under Linux, please refer to :ref:`socketcan`.
+To use these under Linux, please refer to :ref:`SocketCAN`.
Bus
---
@@ -25,7 +25,7 @@ The simplest configuration file would be::
Channels are the URIs used by the underlying API.
-To find available URIs, use :meth:`~can.interface.detect_available_configs`::
+To find available URIs, use :meth:`~can.detect_available_configs`::
configs = can.interface.detect_available_configs(interfaces="etas")
for c in configs:
diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst
index 28fb6f314..02e707c1c 100644
--- a/doc/interfaces/ixxat.rst
+++ b/doc/interfaces/ixxat.rst
@@ -8,7 +8,7 @@ Interface to `IXXAT `__ Virtual CAN Interface V3 SDK. Wor
The Linux ECI SDK is currently unsupported, however on Linux some devices are
supported with :doc:`socketcan`.
-The :meth:`~can.interfaces.ixxat.canlib.IXXATBus.send_periodic` method is supported
+The :meth:`~can.BusABC.send_periodic` method is supported
natively through the on-board cyclic transmit list.
Modifying cyclic messages is not possible. You will need to stop it, and then
start a new periodic message.
@@ -20,7 +20,22 @@ Bus
.. autoclass:: can.interfaces.ixxat.IXXATBus
:members:
-.. autoclass:: can.interfaces.ixxat.canlib.CyclicSendTask
+Implementation based on vcinpl.dll
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus
+ :members:
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask
+ :members:
+
+Implementation based on vcinpl2.dll
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus
+ :members:
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask
:members:
diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst
index a4a51ad09..4e0062cfa 100644
--- a/doc/interfaces/kvaser.rst
+++ b/doc/interfaces/kvaser.rst
@@ -46,3 +46,5 @@ This section contains Kvaser driver specific methods.
.. automethod:: can.interfaces.kvaser.canlib.KvaserBus.get_stats
+.. autoclass:: can.interfaces.kvaser.structures.BusStatistics
+ :members:
diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst
index 05423ac8e..588c5e914 100644
--- a/doc/interfaces/neovi.rst
+++ b/doc/interfaces/neovi.rst
@@ -42,5 +42,6 @@ Bus
---
.. autoclass:: can.interfaces.ics_neovi.NeoViBus
-
-
+.. autoexception:: can.interfaces.ics_neovi.ICSApiError
+.. autoexception:: can.interfaces.ics_neovi.ICSInitializationError
+.. autoexception:: can.interfaces.ics_neovi.ICSOperationError
diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst
index b2214371f..4d2a40717 100644
--- a/doc/interfaces/nican.rst
+++ b/doc/interfaces/nican.rst
@@ -21,6 +21,7 @@ Bus
.. autoclass:: can.interfaces.nican.NicanBus
.. autoexception:: can.interfaces.nican.NicanError
+.. autoexception:: can.interfaces.nican.NicanInitializationError
.. _National Instruments: http://www.ni.com/can/
diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst
index ff82ba9f4..feb40b195 100644
--- a/doc/interfaces/pcan.rst
+++ b/doc/interfaces/pcan.rst
@@ -48,3 +48,4 @@ Bus
---
.. autoclass:: can.interfaces.pcan.PcanBus
+ :members:
diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst
index 5c86fa688..da4d86995 100644
--- a/doc/interfaces/seeedstudio.rst
+++ b/doc/interfaces/seeedstudio.rst
@@ -1,9 +1,8 @@
.. _seeeddoc:
-USB-CAN Analyzer
-================
-...by Seeed Studio
+Seeed Studio USB-CAN Analyzer
+=============================
SKU: 114991193
diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst
index 59ffef21a..99ee54df6 100644
--- a/doc/interfaces/serial.rst
+++ b/doc/interfaces/serial.rst
@@ -21,6 +21,8 @@ Bus
.. autoclass:: can.interfaces.serial.serial_can.SerialBus
+ .. automethod:: _recv_internal
+
Internals
---------
The frames that will be sent and received over the serial interface consist of
diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst
index 1e82d8827..f6cc1ba5f 100644
--- a/doc/interfaces/socketcan.rst
+++ b/doc/interfaces/socketcan.rst
@@ -1,7 +1,9 @@
+.. _SocketCAN:
+
SocketCAN
=========
-The `SocketCAN`_ documentation can be found in the Linux kernel docs at
+The SocketCAN documentation can be found in the `Linux kernel docs`_ at
``networking`` directory. Quoting from the SocketCAN Linux documentation::
> The socketcan package is an implementation of CAN protocols
@@ -284,7 +286,7 @@ to ensure usage of SocketCAN Linux API. The most important differences are:
.. External references
-.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt
+.. _Linux kernel docs: https://www.kernel.org/doc/Documentation/networking/can.txt
.. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module
.. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand
.. _can-utils: https://github.com/linux-can/can-utils
diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst
index e50f134e1..3c05bcc85 100644
--- a/doc/interfaces/socketcand.rst
+++ b/doc/interfaces/socketcand.rst
@@ -28,8 +28,8 @@ daemon running on a remote Raspberry Pi:
except KeyboardInterrupt:
pass
-The output may look like this:
-::
+The output may look like this::
+
Timestamp: 1637791111.209224 ID: 000006fd X Rx DLC: 8 c4 10 e3 2d 96 ff 25 6b
Timestamp: 1637791111.233951 ID: 000001ad X Rx DLC: 4 4d 47 c7 64
Timestamp: 1637791111.409415 ID: 000005f7 X Rx DLC: 8 86 de e6 0f 42 55 5d 39
@@ -47,8 +47,9 @@ However, it will also work with any other socketcan device.
Install CAN Interface for a MCP2515 based interface on a Raspberry Pi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Add the following lines to ``/boot/config.txt``. Please take care on the frequency of the crystal on your MCP2515 board:
-::
+Add the following lines to ``/boot/config.txt``.
+Please take care on the frequency of the crystal on your MCP2515 board::
+
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=1000000
@@ -106,8 +107,8 @@ Run socketcand
./socketcand -v -i can0
-During start, socketcand will prompt its IP address and port it listens to:
-::
+During start, socketcand will prompt its IP address and port it listens to::
+
Verbose output activated
Using network interface 'eth0'
diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst
index e2e8d7517..56243d41d 100644
--- a/doc/interfaces/usb2can.rst
+++ b/doc/interfaces/usb2can.rst
@@ -86,3 +86,5 @@ Internals
.. autoclass:: can.interfaces.usb2can.Usb2CanAbstractionLayer
:members:
:undoc-members:
+
+.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError
diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst
index dcd45f1bf..7b5ede616 100644
--- a/doc/interfaces/vector.rst
+++ b/doc/interfaces/vector.rst
@@ -19,8 +19,6 @@ 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,7 +27,7 @@ Bus
.. autoclass:: can.interfaces.vector.VectorBus
.. autoexception:: can.interfaces.vector.VectorError
-
+.. autoexception:: can.interfaces.vector.VectorInitializationError
+.. autoexception:: can.interfaces.vector.VectorOperationError
.. _Vector: https://vector.com/
-.. _pywin32: https://sourceforge.net/projects/pywin32/
diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst
index 9258c9bbd..29976ed47 100644
--- a/doc/interfaces/virtual.rst
+++ b/doc/interfaces/virtual.rst
@@ -70,8 +70,8 @@ arrive at the recipients exactly once. Both is not guaranteed to hold for the be
these guarantees of message delivery and message ordering. The central servers receive and distribute
the CAN messages to all other bus participants, unlike in a real physical CAN network.
The first intra-process ``virtual`` interface only runs within one Python process, effectively the
-Python instance of :class:`VirtualBus` acts as a central server. Notably the ``udp_multicast`` bus
-does not require a central server.
+Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server.
+Notably the ``udp_multicast`` bus does not require a central server.
**Arbitration and throughput** are two interrelated functions/properties of CAN networks which
are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount
@@ -133,3 +133,5 @@ Bus Class Documentation
.. autoclass:: can.interfaces.virtual.VirtualBus
:members:
+
+ .. automethod:: _detect_available_configs
diff --git a/doc/internal-api.rst b/doc/internal-api.rst
index c43db3394..1367dca50 100644
--- a/doc/internal-api.rst
+++ b/doc/internal-api.rst
@@ -70,7 +70,7 @@ methods:
About the IO module
-------------------
-Handling of the different file formats is implemented in :mod:`can.io`.
+Handling of the different file formats is implemented in ``can.io``.
Each file/IO type is within a separate module and ideally implements both a *Reader* and a *Writer*.
The reader usually extends :class:`can.io.generic.BaseIOHandler`, while
the writer often additionally extends :class:`can.Listener`,
diff --git a/doc/message.rst b/doc/message.rst
index e5745f6b5..78ccc0b50 100644
--- a/doc/message.rst
+++ b/doc/message.rst
@@ -200,3 +200,5 @@ Message
Each of the bytes in the data field (when present) are represented as
two-digit hexadecimal numbers.
+
+ .. automethod:: equals
diff --git a/doc/scripts.rst b/doc/scripts.rst
index ace8c1e39..6b9bdf504 100644
--- a/doc/scripts.rst
+++ b/doc/scripts.rst
@@ -55,6 +55,6 @@ The full usage page can be seen below:
can.logconvert
-----------
+--------------
.. command-output:: python -m can.logconvert -h
diff --git a/setup.cfg b/setup.cfg
index b402ee645..043fff73a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -12,6 +12,7 @@ warn_unused_ignores = True
exclude =
(?x)(
venv
+ |^doc/conf.py$
|^test
|^setup.py$
|^can/interfaces/__init__.py
From 99fea55f1aea868a64b9d6ba34ec3b713c26170b Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 10 Oct 2022 17:19:39 +0200
Subject: [PATCH 147/475] explicitly set supported file formats (#1406)
---
can/io/logger.py | 34 ++++++++++++++++++++--------------
can/io/printer.py | 13 ++++++++++---
test/test_rotating_loggers.py | 2 ++
3 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/can/io/logger.py b/can/io/logger.py
index df13c6256..09312101b 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -7,14 +7,13 @@
from abc import ABC, abstractmethod
from datetime import datetime
import gzip
-from typing import Any, Optional, Callable, Type, Tuple, cast, Dict
+from typing import Any, Optional, Callable, Type, Tuple, cast, Dict, Set
from types import TracebackType
from typing_extensions import Literal
from pkg_resources import iter_entry_points
-import can.io
from ..message import Message
from ..listener import Listener
from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
@@ -131,9 +130,10 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC):
:class:`~logging.handlers.BaseRotatingHandler`.
Subclasses must set the `_writer` attribute upon initialization.
-
"""
+ _supported_formats: Set[str] = set()
+
#: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename`
#: method delegates to this callable. The parameters passed to the callable are
#: those passed to :meth:`~BaseRotatingLogger.rotation_filename`.
@@ -224,17 +224,21 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
:return:
An instance of a writer class.
"""
-
- logger = Logger(filename, *self.writer_args, **self.writer_kwargs)
- if isinstance(logger, FileIOMessageWriter):
- return logger
- elif isinstance(logger, Printer) and logger.file is not None:
- return cast(FileIOMessageWriter, logger)
- else:
- raise Exception(
- f"The log format \"{''.join(pathlib.Path(filename).suffixes[-2:])}"
- f'" is not supported by {self.__class__.__name__}'
- )
+ suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower()
+
+ if suffix in self._supported_formats:
+ logger = Logger(filename, *self.writer_args, **self.writer_kwargs)
+ if isinstance(logger, FileIOMessageWriter):
+ return logger
+ elif isinstance(logger, Printer) and logger.file is not None:
+ return cast(FileIOMessageWriter, logger)
+
+ raise Exception(
+ f'The log format "{suffix}" '
+ f"is not supported by {self.__class__.__name__}. "
+ f"{self.__class__.__name__} supports the following formats: "
+ f"{', '.join(self._supported_formats)}"
+ )
def stop(self) -> None:
"""Stop handling new messages.
@@ -306,6 +310,8 @@ class SizedRotatingLogger(BaseRotatingLogger):
:meth:`~can.Listener.stop` is called.
"""
+ _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"}
+
def __init__(
self,
base_filename: StringPathLike,
diff --git a/can/io/printer.py b/can/io/printer.py
index 61871e8ad..6a43c63b9 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -4,7 +4,7 @@
import logging
-from typing import Optional, TextIO, Union, Any
+from typing import Optional, TextIO, Union, Any, cast
from ..message import Message
from .generic import MessageWriter
@@ -40,11 +40,18 @@ def __init__(
:param append: If set to `True` messages, are appended to the file,
else the file is truncated
"""
+ self.write_to_file = file is not None
mode = "a" if append else "w"
super().__init__(file, mode=mode)
def on_message_received(self, msg: Message) -> None:
- if self.file is not None:
- self.file.write(str(msg) + "\n")
+ if self.write_to_file:
+ cast(TextIO, self.file).write(str(msg) + "\n")
else:
print(msg)
+
+ def file_size(self) -> int:
+ """Return an estimate of the current file size in bytes."""
+ if self.file is not None:
+ return self.file.tell()
+ return 0
diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py
index d900f4f23..ad4388bf7 100644
--- a/test/test_rotating_loggers.py
+++ b/test/test_rotating_loggers.py
@@ -18,6 +18,8 @@ def _get_instance(path, *args, **kwargs) -> can.io.BaseRotatingLogger:
class SubClass(can.io.BaseRotatingLogger):
"""Subclass that implements abstract methods for testing."""
+ _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"}
+
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._writer = can.Printer(file=path / "__unused.txt")
From b587878554fda1c9476788c27ffa1f9881870442 Mon Sep 17 00:00:00 2001
From: Martin Kletzander
Date: Mon, 10 Oct 2022 22:41:25 +0200
Subject: [PATCH 148/475] setup.cfg: Use license_files instead of license_file
(#1408)
Without this (and recent enough setuptools) I get this warning:
/home/nert/dev/python-can/.tox/.package/lib/python3.10/site-packages/setuptools/config/setupcfg.py:508: SetuptoolsDeprecationWarning: The license_file parameter is deprecated, use license_files instead.
---
setup.cfg | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.cfg b/setup.cfg
index 043fff73a..2f3ee032f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[metadata]
-license_file = LICENSE.txt
+license_files = LICENSE.txt
[mypy]
warn_return_any = True
From 0c82d2c442811291c5748306f6767c9a093723d6 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 11 Oct 2022 00:43:52 +0200
Subject: [PATCH 149/475] update github actions (#1409)
---
.github/workflows/build.yml | 47 +++++++++++++++++++++----------
.github/workflows/format-code.yml | 6 ++--
setup.py | 1 +
3 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index aad9274fe..93094017f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,17 +13,21 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
experimental: [false]
- python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
- include:
- # Only test on a single configuration while there are just pre-releases
- - os: ubuntu-latest
- experimental: true
- python-version: "3.11.0-alpha - 3.11.0"
+ python-version: [
+ "3.7",
+ "3.8",
+ "3.9",
+ "3.10",
+ "3.11.0-alpha - 3.11.0",
+ "pypy-3.7",
+ "pypy-3.8",
+ "pypy-3.9",
+ ]
fail-fast: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -34,16 +38,16 @@ jobs:
run: |
tox -e gh
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
static-code-analysis:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
@@ -75,9 +79,9 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
@@ -91,9 +95,9 @@ jobs:
docs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
@@ -109,3 +113,16 @@ jobs:
name: sphinx-out
path: ./build/
retention-days: 5
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Build wheel and sdist
+ run: pipx run build
+ - name: Check build artifacts
+ run: pipx run twine check --strict dist/*
diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml
index b86789662..68c6f56d8 100644
--- a/.github/workflows/format-code.yml
+++ b/.github/workflows/format-code.yml
@@ -9,9 +9,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
@@ -22,7 +22,7 @@ jobs:
run: |
black --verbose .
- name: Commit Formated Code
- uses: EndBug/add-and-commit@v7
+ uses: EndBug/add-and-commit@v9
with:
message: "Format code with black"
# Ref https://git-scm.com/docs/git-add#_examples
diff --git a/setup.py b/setup.py
index adbd61f91..841425482 100644
--- a/setup.py
+++ b/setup.py
@@ -44,6 +44,7 @@
url="https://github.com/hardbyte/python-can",
description="Controller Area Network interface module for Python",
long_description=long_description,
+ long_description_content_type="text/x-rst",
classifiers=[
# a list of all available ones: https://pypi.org/classifiers/
"Programming Language :: Python",
From 4b43f337878091bce1a737b0acf98a4fa2bbd968 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 28 Oct 2022 15:38:44 +0200
Subject: [PATCH 150/475] improve vector documentation (#1420)
---
can/interfaces/vector/__init__.py | 2 +-
can/interfaces/vector/canlib.py | 3 ++
doc/interfaces/vector.rst | 65 +++++++++++++++++++++++++++++--
3 files changed, 66 insertions(+), 4 deletions(-)
diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py
index cdeb1d3cb..f543a6109 100644
--- a/can/interfaces/vector/__init__.py
+++ b/can/interfaces/vector/__init__.py
@@ -1,5 +1,5 @@
"""
"""
-from .canlib import VectorBus, VectorChannelConfig
+from .canlib import VectorBus, VectorChannelConfig, get_channel_configs
from .exceptions import VectorError, VectorOperationError, VectorInitializationError
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index a36f67e9e..8f5c557d6 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -876,6 +876,8 @@ def set_timer_rate(self, timer_rate_ms: int) -> None:
class VectorChannelConfig(NamedTuple):
+ """NamedTuple which contains the channel properties from Vector XL API."""
+
name: str
hwType: xldefine.XL_HardwareType
hwIndex: int
@@ -906,6 +908,7 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig:
def get_channel_configs() -> List[VectorChannelConfig]:
+ """Read channel properties from Vector XL API."""
try:
driver_config = _get_xl_driver_config()
except VectorError:
diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst
index 7b5ede616..a4708c28f 100644
--- a/doc/interfaces/vector.rst
+++ b/doc/interfaces/vector.rst
@@ -4,7 +4,7 @@ Vector
This interface adds support for CAN controllers by `Vector`_. Only Windows is supported.
By default this library uses the channel configuration for CANalyzer.
-To use a different application, open Vector Hardware Config program and create
+To use a different application, open **Vector Hardware Configuration** program and create
a new application and assign the channels you may want to use.
Specify the application name as ``app_name='Your app name'`` when constructing
the bus or in a config file.
@@ -21,13 +21,72 @@ application named "python-can"::
-Bus
----
+VectorBus
+---------
.. autoclass:: can.interfaces.vector.VectorBus
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ set_filters,
+ recv,
+ send,
+ send_periodic,
+ stop_all_periodic_tasks,
+ flush_tx_buffer,
+ reset,
+ shutdown,
+ popup_vector_hw_configuration,
+ get_application_config,
+ set_application_config
+
+Exceptions
+----------
.. autoexception:: can.interfaces.vector.VectorError
+ :show-inheritance:
.. autoexception:: can.interfaces.vector.VectorInitializationError
+ :show-inheritance:
.. autoexception:: can.interfaces.vector.VectorOperationError
+ :show-inheritance:
+
+Miscellaneous
+-------------
+
+.. autofunction:: can.interfaces.vector.get_channel_configs
+
+.. autoclass:: can.interfaces.vector.VectorChannelConfig
+ :show-inheritance:
+ :class-doc-from: class
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_HardwareType
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_ChannelCapabilities
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_BusCapabilities
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_BusTypes
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_Status
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
.. _Vector: https://vector.com/
From d3103d83813e4f077d1b0c010e3b063bc5ad5f58 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 30 Oct 2022 23:25:05 +0100
Subject: [PATCH 151/475] update docs (#1421)
---
can/interface.py | 51 ++++++++++++++++------------
can/interfaces/cantact.py | 2 +-
doc/api.rst | 2 +-
doc/bus.rst | 21 +++---------
doc/conf.py | 1 +
doc/configuration.rst | 45 +++++++++++++++++++------
doc/development.rst | 12 ++++---
doc/doc-requirements.txt | 1 +
doc/interfaces.rst | 65 ++++++++++++++++++++++++++++++------
doc/interfaces/cantact.rst | 8 +++++
doc/interfaces/neousys.rst | 13 ++++++++
doc/internal-api.rst | 4 +--
examples/receive_all.py | 10 +++---
examples/send_one.py | 10 +++---
examples/serial_com.py | 4 +--
examples/vcan_filtered.py | 2 +-
examples/virtual_can_demo.py | 4 +--
17 files changed, 170 insertions(+), 85 deletions(-)
create mode 100644 doc/interfaces/cantact.rst
create mode 100644 doc/interfaces/neousys.rst
diff --git a/can/interface.py b/can/interface.py
index f1a0087e3..527f84d20 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -60,37 +60,44 @@ class Bus(BusABC): # pylint: disable=abstract-method
Instantiates a CAN Bus of the given ``interface``, falls back to reading a
configuration file from default locations.
- """
- @staticmethod
- def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
- cls: Any, channel: Optional[Channel] = None, *args: Any, **kwargs: Any
- ) -> BusABC:
- """
- Takes the same arguments as :class:`can.BusABC.__init__`.
- Some might have a special meaning, see below.
+ :param channel:
+ Channel identification. Expected type is backend dependent.
+ Set to ``None`` to let it be resolved automatically from the default
+ :ref:`configuration`.
- :param channel:
- Set to ``None`` to let it be resolved automatically from the default
- configuration. That might fail, see below.
+ :param interface:
+ See :ref:`interface names` for a list of supported interfaces.
+ Set to ``None`` to let it be resolved automatically from the default
+ :ref:`configuration`.
- Expected type is backend dependent.
+ :param args:
+ ``interface`` specific positional arguments.
- :param dict kwargs:
- Should contain an ``interface`` key with a valid interface name. If not,
- it is completed using :meth:`can.util.load_config`.
+ :param kwargs:
+ ``interface`` specific keyword arguments.
- :raises: can.CanInterfaceNotImplementedError
- if the ``interface`` isn't recognized or cannot be loaded
+ :raises ~can.exceptions.CanInterfaceNotImplementedError:
+ if the ``interface`` isn't recognized or cannot be loaded
- :raises: can.CanInitializationError
- if the bus cannot be instantiated
+ :raises ~can.exceptions.CanInitializationError:
+ if the bus cannot be instantiated
- :raises: ValueError
- if the ``channel`` could not be determined
- """
+ :raises ValueError:
+ if the ``channel`` could not be determined
+ """
+ @staticmethod
+ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
+ cls: Any,
+ channel: Optional[Channel] = None,
+ interface: Optional[str] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> BusABC:
# figure out the rest of the configuration; this might raise an error
+ if interface is not None:
+ kwargs["interface"] = interface
if channel is not None:
kwargs["channel"] = channel
if "context" in kwargs:
diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py
index 056a64a6b..9ad7fbef8 100644
--- a/can/interfaces/cantact.py
+++ b/can/interfaces/cantact.py
@@ -59,7 +59,7 @@ def __init__(
Bitrate in bits/s
:param bool monitor:
If true, operate in listen-only monitoring mode
- :param BitTiming bit_timing
+ :param BitTiming bit_timing:
Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None.
"""
diff --git a/doc/api.rst b/doc/api.rst
index 011553b1b..23342f992 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -1,7 +1,7 @@
Library API
===========
-The main objects are the :class:`~can.BusABC` and the :class:`~can.Message`.
+The main objects are the :class:`~can.Bus` and the :class:`~can.Message`.
A form of CAN interface is also required.
.. hint::
diff --git a/doc/bus.rst b/doc/bus.rst
index 9f7077cc1..4db49ee29 100644
--- a/doc/bus.rst
+++ b/doc/bus.rst
@@ -15,24 +15,11 @@ and implements the :class:`~can.BusABC` API.
A thread safe bus wrapper is also available, see `Thread safe bus`_.
-Autoconfig Bus
-''''''''''''''
-
.. autoclass:: can.Bus
+ :class-doc-from: class
+ :show-inheritance:
:members:
-
-
-API
-'''
-
-.. autoclass:: can.BusABC
- :members:
-
- .. automethod:: __iter__
- .. automethod:: _recv_internal
- .. automethod:: _apply_filters
- .. automethod:: _detect_available_configs
- .. automethod:: _send_periodic_internal
+ :inherited-members:
.. autoclass:: can.bus.BusState
:members:
@@ -81,7 +68,7 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass
See :meth:`~can.BusABC.set_filters` for the implementation.
Thread safe bus
----------------
+'''''''''''''''
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.
diff --git a/doc/conf.py b/doc/conf.py
index 495416078..c1409b8c2 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,6 +48,7 @@
"sphinx.ext.viewcode",
"sphinx.ext.graphviz",
"sphinxcontrib.programoutput",
+ "sphinx_inline_tabs",
"sphinx_rtd_theme",
]
diff --git a/doc/configuration.rst b/doc/configuration.rst
index 9bda3030f..d92d6164f 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -1,3 +1,5 @@
+.. _configuration:
+
Configuration
=============
@@ -100,6 +102,7 @@ For example:
``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}``
+.. _interface names:
Interface Names
---------------
@@ -109,31 +112,51 @@ Lookup table of interface names:
+---------------------+-------------------------------------+
| Name | Documentation |
+=====================+=====================================+
-| ``"socketcan"`` | :doc:`interfaces/socketcan` |
+| ``"canalystii"`` | :doc:`interfaces/canalystii` |
+---------------------+-------------------------------------+
-| ``"kvaser"`` | :doc:`interfaces/kvaser` |
+| ``"cantact"`` | :doc:`interfaces/cantact` |
+---------------------+-------------------------------------+
-| ``"serial"`` | :doc:`interfaces/serial` |
+| ``"etas"`` | :doc:`interfaces/etas` |
+---------------------+-------------------------------------+
-| ``"slcan"`` | :doc:`interfaces/slcan` |
+| ``"gs_usb"`` | :doc:`interfaces/gs_usb` |
++---------------------+-------------------------------------+
+| ``"iscan"`` | :doc:`interfaces/iscan` |
+---------------------+-------------------------------------+
| ``"ixxat"`` | :doc:`interfaces/ixxat` |
+---------------------+-------------------------------------+
-| ``"pcan"`` | :doc:`interfaces/pcan` |
+| ``"kvaser"`` | :doc:`interfaces/kvaser` |
+---------------------+-------------------------------------+
-| ``"usb2can"`` | :doc:`interfaces/usb2can` |
+| ``"neousys"`` | :doc:`interfaces/neousys` |
++---------------------+-------------------------------------+
+| ``"neovi"`` | :doc:`interfaces/neovi` |
+---------------------+-------------------------------------+
| ``"nican"`` | :doc:`interfaces/nican` |
+---------------------+-------------------------------------+
-| ``"iscan"`` | :doc:`interfaces/iscan` |
+| ``"nixnet"`` | :doc:`interfaces/nixnet` |
+---------------------+-------------------------------------+
-| ``"neovi"`` | :doc:`interfaces/neovi` |
+| ``"pcan"`` | :doc:`interfaces/pcan` |
+---------------------+-------------------------------------+
-| ``"vector"`` | :doc:`interfaces/vector` |
+| ``"robotell"`` | :doc:`interfaces/robotell` |
+---------------------+-------------------------------------+
-| ``"virtual"`` | :doc:`interfaces/virtual` |
+| ``"seeedstudio"`` | :doc:`interfaces/seeedstudio` |
+---------------------+-------------------------------------+
-| ``"canalystii"`` | :doc:`interfaces/canalystii` |
+| ``"serial"`` | :doc:`interfaces/serial` |
++---------------------+-------------------------------------+
+| ``"slcan"`` | :doc:`interfaces/slcan` |
++---------------------+-------------------------------------+
+| ``"socketcan"`` | :doc:`interfaces/socketcan` |
++---------------------+-------------------------------------+
+| ``"socketcand"`` | :doc:`interfaces/socketcand` |
+---------------------+-------------------------------------+
| ``"systec"`` | :doc:`interfaces/systec` |
+---------------------+-------------------------------------+
+| ``"udp_multicast"`` | :doc:`interfaces/udp_multicast` |
++---------------------+-------------------------------------+
+| ``"usb2can"`` | :doc:`interfaces/usb2can` |
++---------------------+-------------------------------------+
+| ``"vector"`` | :doc:`interfaces/vector` |
++---------------------+-------------------------------------+
+| ``"virtual"`` | :doc:`interfaces/virtual` |
++---------------------+-------------------------------------+
+
+Additional interface types can be added via the :ref:`plugin interface`.
\ No newline at end of file
diff --git a/doc/development.rst b/doc/development.rst
index 03bf9a374..055401bdc 100644
--- a/doc/development.rst
+++ b/doc/development.rst
@@ -35,8 +35,8 @@ The following assumes that the commands are executed from the root of the reposi
The project can be built with::
- pip install wheel
- python setup.py sdist bdist_wheel
+ pipx run build
+ pipx run twine check dist/*
The project can be installed in editable mode with::
@@ -44,8 +44,7 @@ The project can be installed in editable mode with::
The unit tests can be run with::
- pip install tox
- tox -e py
+ pipx run tox -e py
The documentation can be built with::
@@ -79,6 +78,11 @@ These steps are a guideline on how to add a new backend to python-can.
To get started, have a look at ``back2back_test.py``:
Simply add a test case like ``BasicTestSocketCan`` and some basic tests will be executed for the new interface.
+.. attention::
+ We strongly recommend using the :ref:`plugin interface` to extend python-can.
+ Publish a python package that contains your :class:`can.BusABC` subclass and use
+ it within the python-can API. We will mention your package inside this documentation
+ and add it as an optional dependency.
Code Structure
--------------
diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt
index 9732ea4ef..b1c2da632 100644
--- a/doc/doc-requirements.txt
+++ b/doc/doc-requirements.txt
@@ -1,3 +1,4 @@
sphinx>=5.2.3
sphinxcontrib-programoutput
sphinx_rtd_theme
+sphinx-inline-tabs
diff --git a/doc/interfaces.rst b/doc/interfaces.rst
index dbe4ad426..c25ea8bea 100644
--- a/doc/interfaces.rst
+++ b/doc/interfaces.rst
@@ -1,3 +1,5 @@
+.. _can interface modules:
+
CAN Interface Modules
---------------------
@@ -12,11 +14,13 @@ The available interfaces are:
:maxdepth: 1
interfaces/canalystii
+ interfaces/cantact
interfaces/etas
interfaces/gs_usb
interfaces/iscan
interfaces/ixxat
interfaces/kvaser
+ interfaces/neousys
interfaces/neovi
interfaces/nican
interfaces/nixnet
@@ -33,19 +37,58 @@ The available interfaces are:
interfaces/vector
interfaces/virtual
-Additional interfaces can be added via a plugin interface. An external package
-can register a new interface by using the ``can.interface`` entry point in its setup.py.
+The *Interface Names* are listed in :doc:`configuration`.
-The format of the entry point is ``interface_name=module:classname`` where
-``classname`` is a concrete :class:`can.BusABC` implementation.
-::
+.. _plugin interface:
- entry_points={
- 'can.interface': [
- "interface_name=module:classname",
- ]
- },
+Plugin Interface
+^^^^^^^^^^^^^^^^
+External packages can register a new interfaces by using the ``can.interface`` entry point
+in its project configuration. The format of the entry point depends on your project
+configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*).
-The *Interface Names* are listed in :doc:`configuration`.
+In the following example ``module`` defines the location of your bus class inside your
+package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of
+your :class:`can.BusABC` subclass.
+
+.. tab:: pyproject.toml (PEP 621)
+
+ .. code-block:: toml
+
+ # Note the quotes around can.interface in order to escape the dot .
+ [project.entry-points."can.interface"]
+ interface_name = "module:classname"
+
+.. tab:: setup.cfg
+
+ .. code-block:: ini
+
+ [options.entry_points]
+ can.interface =
+ interface_name = module:classname
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ from setuptools import setup
+
+ setup(
+ # ...,
+ entry_points = {
+ 'can.interface': [
+ 'interface_name = module:classname'
+ ]
+ }
+ )
+
+The ``interface_name`` can be used to
+create an instance of the bus in the **python-can** API:
+
+.. code-block:: python
+
+ import can
+
+ bus = can.Bus(interface="interface_name", channel=0)
diff --git a/doc/interfaces/cantact.rst b/doc/interfaces/cantact.rst
new file mode 100644
index 000000000..dc9667218
--- /dev/null
+++ b/doc/interfaces/cantact.rst
@@ -0,0 +1,8 @@
+CANtact CAN Interface
+=====================
+
+Interface for CANtact devices from Linklayer Labs
+
+.. autoclass:: can.interfaces.cantact.CantactBus
+ :show-inheritance:
+ :members:
diff --git a/doc/interfaces/neousys.rst b/doc/interfaces/neousys.rst
new file mode 100644
index 000000000..97a37868c
--- /dev/null
+++ b/doc/interfaces/neousys.rst
@@ -0,0 +1,13 @@
+Neousys CAN Interface
+=====================
+
+This kind of interface can be found for example on Neousys POC-551VTC
+One needs to have correct drivers and DLL (Share object for Linux) from
+`Neousys `_.
+
+Beware this is only tested on Linux kernel higher than v5.3. This should be drop in
+with Windows but you have to replace with correct named DLL
+
+.. autoclass:: can.interfaces.neousys.NeousysBus
+ :show-inheritance:
+ :members:
diff --git a/doc/internal-api.rst b/doc/internal-api.rst
index 1367dca50..3ef599598 100644
--- a/doc/internal-api.rst
+++ b/doc/internal-api.rst
@@ -55,15 +55,15 @@ configuration into account.
Bus Internals
~~~~~~~~~~~~~
-Several methods are not documented in the main :class:`can.BusABC`
+Several methods are not documented in the main :class:`can.Bus`
as they are primarily useful for library developers as opposed to
library users. This is the entire ABC bus class with all internal
methods:
.. autoclass:: can.BusABC
+ :members:
:private-members:
:special-members:
- :noindex:
diff --git a/examples/receive_all.py b/examples/receive_all.py
index 7ff532079..7b94d526f 100755
--- a/examples/receive_all.py
+++ b/examples/receive_all.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
"""
-Shows how the receive messages via polling.
+Shows how to receive messages via polling.
"""
import can
@@ -11,11 +11,9 @@
def receive_all():
"""Receives all messages and prints them to the console until Ctrl+C is pressed."""
- with can.interface.Bus(
- bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000
- ) as bus:
- # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000)
- # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
+ with can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=250000) as bus:
+ # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000)
+ # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000)
# set to read-only, only supported on some interfaces
bus.state = BusState.PASSIVE
diff --git a/examples/send_one.py b/examples/send_one.py
index 49a4f1ee1..7e3fb8a4c 100755
--- a/examples/send_one.py
+++ b/examples/send_one.py
@@ -12,13 +12,13 @@ def send_one():
# this uses the default configuration (for example from the config file)
# see https://python-can.readthedocs.io/en/stable/configuration.html
- with can.interface.Bus() as bus:
+ with can.Bus() as 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)
+ # bus = can.Bus(interface='socketcan', channel='vcan0', bitrate=250000)
+ # bus = can.Bus(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000)
+ # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000)
+ # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000)
# ...
msg = can.Message(
diff --git a/examples/serial_com.py b/examples/serial_com.py
index c57207a77..76b95c3e7 100755
--- a/examples/serial_com.py
+++ b/examples/serial_com.py
@@ -48,8 +48,8 @@ def receive(bus, stop_event):
def main():
"""Controls the sender and receiver."""
- with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server:
- with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client:
+ with can.Bus(interface="serial", channel="/dev/ttyS10") as server:
+ with can.Bus(interface="serial", channel="/dev/ttyS11") as client:
tx_msg = can.Message(
arbitration_id=0x01,
diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py
index fa6c71547..a43fbe821 100755
--- a/examples/vcan_filtered.py
+++ b/examples/vcan_filtered.py
@@ -11,7 +11,7 @@
def main():
"""Send some messages to itself and apply filtering."""
- with can.Bus(bustype="virtual", receive_own_messages=True) as bus:
+ with can.Bus(interface="virtual", receive_own_messages=True) as bus:
can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}]
bus.set_filters(can_filters)
diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py
index d0d6a4a6a..af50a87a7 100755
--- a/examples/virtual_can_demo.py
+++ b/examples/virtual_can_demo.py
@@ -14,9 +14,9 @@ def producer(thread_id: int, message_count: int = 16) -> None:
"""Spam the bus with messages including the data id.
:param thread_id: the id of the thread/process
- :param message_count: the number of messages that shall be send
+ :param message_count: the number of messages that shall be sent
"""
- with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore
+ with can.Bus(interface="socketcan", channel="vcan0") as bus: # type: ignore
for i in range(message_count):
msg = can.Message(
arbitration_id=0x0CF02200 + thread_id,
From fb892d6f3b24ad57ad37e97ed7031950009ad423 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 31 Oct 2022 01:51:32 +0100
Subject: [PATCH 152/475] add busParams and use snake case for
VectorChannelConfig (#1422)
---
can/interfaces/vector/__init__.py | 9 +-
can/interfaces/vector/canlib.py | 140 ++++++++++++++++++++++--------
doc/interfaces/vector.rst | 24 +++++
test/test_vector.py | 56 +++++++++---
4 files changed, 179 insertions(+), 50 deletions(-)
diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py
index f543a6109..c5eae7140 100644
--- a/can/interfaces/vector/__init__.py
+++ b/can/interfaces/vector/__init__.py
@@ -1,5 +1,12 @@
"""
"""
-from .canlib import VectorBus, VectorChannelConfig, get_channel_configs
+from .canlib import (
+ VectorBus,
+ get_channel_configs,
+ VectorChannelConfig,
+ VectorBusParams,
+ VectorCanParams,
+ VectorCanFdParams,
+)
from .exceptions import VectorError, VectorOperationError, VectorInitializationError
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 8f5c557d6..d015f2407 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -295,12 +295,12 @@ def _find_global_channel_idx(
if serial is not None:
hw_type: Optional[xldefine.XL_HardwareType] = None
for channel_config in channel_configs:
- if channel_config.serialNumber != serial:
+ if channel_config.serial_number != serial:
continue
- hw_type = xldefine.XL_HardwareType(channel_config.hwType)
- if channel_config.hwChannel == channel:
- return channel_config.channelIndex
+ hw_type = xldefine.XL_HardwareType(channel_config.hw_type)
+ if channel_config.hw_channel == channel:
+ return channel_config.channel_index
if hw_type is None:
err_msg = f"No interface with serial {serial} found."
@@ -331,7 +331,7 @@ def _find_global_channel_idx(
# check if channel is a valid global channel index
for channel_config in channel_configs:
- if channel == channel_config.channelIndex:
+ if channel == channel_config.channel_index:
return channel
raise CanInitializationError(
@@ -727,26 +727,28 @@ def _detect_available_configs() -> List[AutoDetectedConfig]:
LOG.info("Found %d channels", len(channel_configs))
for channel_config in channel_configs:
if (
- not channel_config.channelBusCapabilities
+ not channel_config.channel_bus_capabilities
& xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN
):
continue
LOG.info(
- "Channel index %d: %s", channel_config.channelIndex, channel_config.name
+ "Channel index %d: %s",
+ channel_config.channel_index,
+ channel_config.name,
)
configs.append(
{
# data for use in VectorBus.__init__():
"interface": "vector",
- "channel": channel_config.hwChannel,
- "serial": channel_config.serialNumber,
+ "channel": channel_config.hw_channel,
+ "serial": channel_config.serial_number,
# data for use in VectorBus.set_application_config():
- "hw_type": channel_config.hwType,
- "hw_index": channel_config.hwIndex,
- "hw_channel": channel_config.hwChannel,
+ "hw_type": channel_config.hw_type,
+ "hw_index": channel_config.hw_index,
+ "hw_channel": channel_config.hw_channel,
# additional information:
"supports_fd": bool(
- channel_config.channelCapabilities
+ channel_config.channel_capabilities
& xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT
),
"vector_channel_config": channel_config,
@@ -875,22 +877,53 @@ def set_timer_rate(self, timer_rate_ms: int) -> None:
self.xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us)
+class VectorCanParams(NamedTuple):
+ bitrate: int
+ sjw: int
+ tseg1: int
+ tseg2: int
+ sam: int
+ output_mode: xldefine.XL_OutputMode
+ can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode
+
+
+class VectorCanFdParams(NamedTuple):
+ bitrate: int
+ data_bitrate: int
+ sjw_abr: int
+ tseg1_abr: int
+ tseg2_abr: int
+ sam_abr: int
+ sjw_dbr: int
+ tseg1_dbr: int
+ tseg2_dbr: int
+ output_mode: xldefine.XL_OutputMode
+ can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode
+
+
+class VectorBusParams(NamedTuple):
+ bus_type: xldefine.XL_BusTypes
+ can: VectorCanParams
+ canfd: VectorCanFdParams
+
+
class VectorChannelConfig(NamedTuple):
"""NamedTuple which contains the channel properties from Vector XL API."""
name: str
- hwType: xldefine.XL_HardwareType
- hwIndex: int
- hwChannel: int
- channelIndex: int
- channelMask: int
- channelCapabilities: xldefine.XL_ChannelCapabilities
- channelBusCapabilities: xldefine.XL_BusCapabilities
- isOnBus: bool
- connectedBusType: xldefine.XL_BusTypes
- serialNumber: int
- articleNumber: int
- transceiverName: str
+ hw_type: xldefine.XL_HardwareType
+ hw_index: int
+ hw_channel: int
+ channel_index: int
+ channel_mask: int
+ channel_capabilities: xldefine.XL_ChannelCapabilities
+ channel_bus_capabilities: xldefine.XL_BusCapabilities
+ is_on_bus: bool
+ connected_bus_type: xldefine.XL_BusTypes
+ bus_params: VectorBusParams
+ serial_number: int
+ article_number: int
+ transceiver_name: str
def _get_xl_driver_config() -> xlclass.XLdriverConfig:
@@ -907,6 +940,38 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig:
return driver_config
+def _read_bus_params_from_c_struct(bus_params: xlclass.XLbusParams) -> VectorBusParams:
+ return VectorBusParams(
+ bus_type=xldefine.XL_BusTypes(bus_params.busType),
+ can=VectorCanParams(
+ bitrate=bus_params.data.can.bitRate,
+ sjw=bus_params.data.can.sjw,
+ tseg1=bus_params.data.can.tseg1,
+ tseg2=bus_params.data.can.tseg2,
+ sam=bus_params.data.can.sam,
+ output_mode=xldefine.XL_OutputMode(bus_params.data.can.outputMode),
+ can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode(
+ bus_params.data.can.canOpMode
+ ),
+ ),
+ canfd=VectorCanFdParams(
+ bitrate=bus_params.data.canFD.arbitrationBitRate,
+ data_bitrate=bus_params.data.canFD.dataBitRate,
+ sjw_abr=bus_params.data.canFD.sjwAbr,
+ tseg1_abr=bus_params.data.canFD.tseg1Abr,
+ tseg2_abr=bus_params.data.canFD.tseg2Abr,
+ sam_abr=bus_params.data.canFD.samAbr,
+ sjw_dbr=bus_params.data.canFD.sjwDbr,
+ tseg1_dbr=bus_params.data.canFD.tseg1Dbr,
+ tseg2_dbr=bus_params.data.canFD.tseg2Dbr,
+ output_mode=xldefine.XL_OutputMode(bus_params.data.canFD.outputMode),
+ can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode(
+ bus_params.data.canFD.canOpMode
+ ),
+ ),
+ )
+
+
def get_channel_configs() -> List[VectorChannelConfig]:
"""Read channel properties from Vector XL API."""
try:
@@ -919,22 +984,23 @@ def get_channel_configs() -> List[VectorChannelConfig]:
xlcc: xlclass.XLchannelConfig = driver_config.channel[i]
vcc = VectorChannelConfig(
name=xlcc.name.decode(),
- hwType=xldefine.XL_HardwareType(xlcc.hwType),
- hwIndex=xlcc.hwIndex,
- hwChannel=xlcc.hwChannel,
- channelIndex=xlcc.channelIndex,
- channelMask=xlcc.channelMask,
- channelCapabilities=xldefine.XL_ChannelCapabilities(
+ hw_type=xldefine.XL_HardwareType(xlcc.hwType),
+ hw_index=xlcc.hwIndex,
+ hw_channel=xlcc.hwChannel,
+ channel_index=xlcc.channelIndex,
+ channel_mask=xlcc.channelMask,
+ channel_capabilities=xldefine.XL_ChannelCapabilities(
xlcc.channelCapabilities
),
- channelBusCapabilities=xldefine.XL_BusCapabilities(
+ channel_bus_capabilities=xldefine.XL_BusCapabilities(
xlcc.channelBusCapabilities
),
- isOnBus=bool(xlcc.isOnBus),
- connectedBusType=xldefine.XL_BusTypes(xlcc.connectedBusType),
- serialNumber=xlcc.serialNumber,
- articleNumber=xlcc.articleNumber,
- transceiverName=xlcc.transceiverName.decode(),
+ is_on_bus=bool(xlcc.isOnBus),
+ bus_params=_read_bus_params_from_c_struct(xlcc.busParams),
+ connected_bus_type=xldefine.XL_BusTypes(xlcc.connectedBusType),
+ serial_number=xlcc.serialNumber,
+ article_number=xlcc.articleNumber,
+ transceiver_name=xlcc.transceiverName.decode(),
)
channel_list.append(vcc)
return channel_list
diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst
index a4708c28f..7f9aa1f3f 100644
--- a/doc/interfaces/vector.rst
+++ b/doc/interfaces/vector.rst
@@ -59,6 +59,18 @@ Miscellaneous
:show-inheritance:
:class-doc-from: class
+.. autoclass:: can.interfaces.vector.canlib.VectorBusParams
+ :show-inheritance:
+ :class-doc-from: class
+
+.. autoclass:: can.interfaces.vector.canlib.VectorCanParams
+ :show-inheritance:
+ :class-doc-from: class
+
+.. autoclass:: can.interfaces.vector.canlib.VectorCanFdParams
+ :show-inheritance:
+ :class-doc-from: class
+
.. autoclass:: can.interfaces.vector.xldefine.XL_HardwareType
:show-inheritance:
:member-order: bysource
@@ -83,6 +95,18 @@ Miscellaneous
:members:
:undoc-members:
+.. autoclass:: can.interfaces.vector.xldefine.XL_OutputMode
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
+.. autoclass:: can.interfaces.vector.xldefine.XL_CANFD_BusParams_CanOpMode
+ :show-inheritance:
+ :member-order: bysource
+ :members:
+ :undoc-members:
+
.. autoclass:: can.interfaces.vector.xldefine.XL_Status
:show-inheritance:
:member-order: bysource
diff --git a/test/test_vector.py b/test/test_vector.py
index c4ae21f4e..9aea76281 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -22,6 +22,7 @@
VectorOperationError,
VectorChannelConfig,
)
+from can.interfaces.vector import VectorBusParams, VectorCanParams, VectorCanFdParams
from test.config import IS_WINDOWS
XLDRIVER_FOUND = canlib.xldriver is not None
@@ -604,18 +605,49 @@ def test_winapi_availability() -> None:
def test_vector_channel_config_attributes():
assert hasattr(VectorChannelConfig, "name")
- assert hasattr(VectorChannelConfig, "hwType")
- assert hasattr(VectorChannelConfig, "hwIndex")
- assert hasattr(VectorChannelConfig, "hwChannel")
- assert hasattr(VectorChannelConfig, "channelIndex")
- assert hasattr(VectorChannelConfig, "channelMask")
- assert hasattr(VectorChannelConfig, "channelCapabilities")
- assert hasattr(VectorChannelConfig, "channelBusCapabilities")
- assert hasattr(VectorChannelConfig, "isOnBus")
- assert hasattr(VectorChannelConfig, "connectedBusType")
- assert hasattr(VectorChannelConfig, "serialNumber")
- assert hasattr(VectorChannelConfig, "articleNumber")
- assert hasattr(VectorChannelConfig, "transceiverName")
+ assert hasattr(VectorChannelConfig, "hw_type")
+ assert hasattr(VectorChannelConfig, "hw_index")
+ assert hasattr(VectorChannelConfig, "hw_channel")
+ assert hasattr(VectorChannelConfig, "channel_index")
+ assert hasattr(VectorChannelConfig, "channel_mask")
+ assert hasattr(VectorChannelConfig, "channel_capabilities")
+ assert hasattr(VectorChannelConfig, "channel_bus_capabilities")
+ assert hasattr(VectorChannelConfig, "is_on_bus")
+ assert hasattr(VectorChannelConfig, "bus_params")
+ assert hasattr(VectorChannelConfig, "connected_bus_type")
+ assert hasattr(VectorChannelConfig, "serial_number")
+ assert hasattr(VectorChannelConfig, "article_number")
+ assert hasattr(VectorChannelConfig, "transceiver_name")
+
+
+def test_vector_bus_params_attributes():
+ assert hasattr(VectorBusParams, "bus_type")
+ assert hasattr(VectorBusParams, "can")
+ assert hasattr(VectorBusParams, "canfd")
+
+
+def test_vector_can_params_attributes():
+ assert hasattr(VectorCanParams, "bitrate")
+ assert hasattr(VectorCanParams, "sjw")
+ assert hasattr(VectorCanParams, "tseg1")
+ assert hasattr(VectorCanParams, "tseg2")
+ assert hasattr(VectorCanParams, "sam")
+ assert hasattr(VectorCanParams, "output_mode")
+ assert hasattr(VectorCanParams, "can_op_mode")
+
+
+def test_vector_canfd_params_attributes():
+ assert hasattr(VectorCanFdParams, "bitrate")
+ assert hasattr(VectorCanFdParams, "data_bitrate")
+ assert hasattr(VectorCanFdParams, "sjw_abr")
+ assert hasattr(VectorCanFdParams, "tseg1_abr")
+ assert hasattr(VectorCanFdParams, "tseg2_abr")
+ assert hasattr(VectorCanFdParams, "sam_abr")
+ assert hasattr(VectorCanFdParams, "sjw_dbr")
+ assert hasattr(VectorCanFdParams, "tseg1_dbr")
+ assert hasattr(VectorCanFdParams, "tseg2_dbr")
+ assert hasattr(VectorCanFdParams, "output_mode")
+ assert hasattr(VectorCanFdParams, "can_op_mode")
# *****************************************************************************
From 9d2620bea467352def01e3898e5da9e63a2a82e4 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 31 Oct 2022 02:00:30 +0100
Subject: [PATCH 153/475] Add python 3.11 to supported versions (#1423)
---
.github/workflows/build.yml | 5 ++++-
setup.py | 1 +
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 93094017f..39eae343f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,7 +18,7 @@ jobs:
"3.8",
"3.9",
"3.10",
- "3.11.0-alpha - 3.11.0",
+ "3.11",
"pypy-3.7",
"pypy-3.8",
"pypy-3.9",
@@ -67,6 +67,9 @@ jobs:
- name: mypy 3.10
run: |
mypy --python-version 3.10 .
+ - name: mypy 3.11
+ run: |
+ mypy --python-version 3.11 .
- name: pylint
run: |
pylint --rcfile=.pylintrc \
diff --git a/setup.py b/setup.py
index 841425482..a32471844 100644
--- a/setup.py
+++ b/setup.py
@@ -52,6 +52,7 @@
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
"Natural Language :: English",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
From c9d6d4e287975154d776f555aa509edbda1a3e66 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 31 Oct 2022 03:40:50 +0100
Subject: [PATCH 154/475] fix typo
---
doc/interfaces.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/interfaces.rst b/doc/interfaces.rst
index c25ea8bea..54d70ca86 100644
--- a/doc/interfaces.rst
+++ b/doc/interfaces.rst
@@ -45,7 +45,7 @@ The *Interface Names* are listed in :doc:`configuration`.
Plugin Interface
^^^^^^^^^^^^^^^^
-External packages can register a new interfaces by using the ``can.interface`` entry point
+External packages can register new interfaces by using the ``can.interface`` entry point
in its project configuration. The format of the entry point depends on your project
configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*).
From a344a1305d248a9a065aada119ef90ef4951c491 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 31 Oct 2022 14:08:19 +0100
Subject: [PATCH 155/475] Fix #1376 (#1412)
* try to fix test
* find u32
---
test/test_socketcan.py | 61 +++++++++++-------------------------------
1 file changed, 16 insertions(+), 45 deletions(-)
diff --git a/test/test_socketcan.py b/test/test_socketcan.py
index a2c4faed3..1c38e1583 100644
--- a/test/test_socketcan.py
+++ b/test/test_socketcan.py
@@ -3,13 +3,10 @@
"""
Test functions in `can.interfaces.socketcan.socketcan`.
"""
+import ctypes
+import struct
import unittest
-
-from unittest.mock import Mock
from unittest.mock import patch
-from unittest.mock import call
-
-import ctypes
from can.interfaces.socketcan.socketcan import (
bcm_header_factory,
@@ -240,51 +237,25 @@ def side_effect_ctypes_alignment(value):
]
self.assertEqual(expected_fields, BcmMsgHead._fields_)
- @unittest.skipIf(
- not (
- ctypes.sizeof(ctypes.c_long) == 4 and ctypes.alignment(ctypes.c_long) == 4
- ),
- "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4",
- )
- def test_build_bcm_header_sizeof_long_4_alignof_long_4(self):
- expected_result = (
- b"\x02\x00\x00\x00\x00\x00\x00\x00"
- b"\x00\x00\x00\x00\x00\x00\x00\x00"
- b"\x00\x00\x00\x00\x00\x00\x00\x00"
- b"\x00\x00\x00\x00\x01\x04\x00\x00"
- b"\x01\x00\x00\x00\x00\x00\x00\x00"
- )
+ def test_build_bcm_header(self):
+ def _find_u32_fmt_char() -> str:
+ for _fmt in ("H", "I", "L", "Q"):
+ if struct.calcsize(_fmt) == 4:
+ return _fmt
- self.assertEqual(
- expected_result,
- build_bcm_header(
- opcode=CAN_BCM_TX_DELETE,
- flags=0,
- count=0,
- ival1_seconds=0,
- ival1_usec=0,
- ival2_seconds=0,
- ival2_usec=0,
- can_id=0x401,
- nframes=1,
- ),
- )
+ def _standard_size_little_endian_to_native(data: bytes) -> bytes:
+ std_le_fmt = "
Date: Mon, 31 Oct 2022 14:39:14 +0100
Subject: [PATCH 156/475] Fix #1424 (#1425)
---
test/test_vector.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/test_vector.py b/test/test_vector.py
index 9aea76281..619277443 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -7,6 +7,7 @@
import ctypes
import functools
import pickle
+import sys
import time
from unittest.mock import Mock
@@ -587,6 +588,9 @@ def test_vector_subtype_error_from_generic() -> None:
raise specific
+@pytest.mark.skipif(
+ sys.byteorder != "little", reason="Test relies on little endian data."
+)
def test_get_channel_configs() -> None:
_original_func = canlib._get_xl_driver_config
canlib._get_xl_driver_config = _get_predefined_xl_driver_config
From cd51ec4c07041c7b3a15b999aa4be5ec3829b05e Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 31 Oct 2022 15:47:32 +0100
Subject: [PATCH 157/475] check whether CAN settings were correctly applied
(#1426)
---
can/interfaces/vector/canlib.py | 66 ++++++++++++++++++++++++++++++++-
test/test_vector.py | 7 +++-
2 files changed, 69 insertions(+), 4 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index d015f2407..8f699f3f7 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -140,7 +140,7 @@ def __init__(
:raise ~can.exceptions.CanInterfaceNotImplementedError:
If the current operating system is not supported or the driver could not be loaded.
- :raise can.exceptions.CanInitializationError:
+ :raise ~can.exceptions.CanInitializationError:
If the bus could not be set up.
This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`.
"""
@@ -218,6 +218,7 @@ def __init__(
interface_version,
xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
)
+ self.permission_mask = permission_mask.value
LOG.debug(
"Open Port: PortHandle: %d, PermissionMask: 0x%X",
@@ -225,8 +226,9 @@ def __init__(
permission_mask.value,
)
+ # set CAN settings
for channel in self.channels:
- if permission_mask.value & self.channel_masks[channel]:
+ if self._has_init_access(channel):
if fd:
self._set_bitrate_canfd(
channel=channel,
@@ -242,6 +244,51 @@ def __init__(
elif bitrate:
self._set_bitrate_can(channel=channel, bitrate=bitrate)
+ # Check CAN settings
+ for channel in self.channels:
+ if kwargs.get("_testing", False):
+ # avoid check if xldriver is mocked for testing
+ break
+
+ bus_params = self._read_bus_params(channel)
+ if fd:
+ _canfd = bus_params.canfd
+ if not all(
+ [
+ bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
+ _canfd.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD,
+ _canfd.bitrate == bitrate if bitrate else True,
+ _canfd.sjw_abr == sjw_abr if bitrate else True,
+ _canfd.tseg1_abr == tseg1_abr if bitrate else True,
+ _canfd.tseg2_abr == tseg2_abr if bitrate else True,
+ _canfd.data_bitrate == data_bitrate if data_bitrate else True,
+ _canfd.sjw_dbr == sjw_dbr if data_bitrate else True,
+ _canfd.tseg1_dbr == tseg1_dbr if data_bitrate else True,
+ _canfd.tseg2_dbr == tseg2_dbr if data_bitrate else True,
+ ]
+ ):
+ raise CanInitializationError(
+ f"The requested CAN FD settings could not be set for channel {channel}. "
+ f"Another application might have set incompatible settings. "
+ f"These are the currently active settings: {_canfd._asdict()}"
+ )
+ else:
+ _can = bus_params.can
+ if not all(
+ [
+ bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
+ _can.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20,
+ _can.bitrate == bitrate if bitrate else True,
+ ]
+ ):
+ raise CanInitializationError(
+ f"The requested CAN settings could not be set for channel {channel}. "
+ f"Another application might have set incompatible settings. "
+ f"These are the currently active settings: {_can._asdict()}"
+ )
+
# Enable/disable TX receipts
tx_receipts = 1 if receive_own_messages else 0
self.xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0)
@@ -340,6 +387,21 @@ def _find_global_channel_idx(
error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT,
)
+ def _has_init_access(self, channel: int) -> bool:
+ return bool(self.permission_mask & self.channel_masks[channel])
+
+ def _read_bus_params(self, channel: int) -> "VectorBusParams":
+ channel_mask = self.channel_masks[channel]
+
+ vcc_list = get_channel_configs()
+ for vcc in vcc_list:
+ if vcc.channel_mask == channel_mask:
+ return vcc.bus_params
+
+ raise CanInitializationError(
+ f"Channel configuration for channel {channel} not found."
+ )
+
def _set_bitrate_can(
self,
channel: int,
diff --git a/test/test_vector.py b/test/test_vector.py
index 619277443..b0f305821 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -62,6 +62,7 @@ def mock_xldriver() -> None:
# backup unmodified values
real_xldriver = canlib.xldriver
real_waitforsingleobject = canlib.WaitForSingleObject
+ real_has_events = canlib.HAS_EVENTS
# set mock
canlib.xldriver = xldriver_mock
@@ -72,6 +73,7 @@ def mock_xldriver() -> None:
# cleanup
canlib.xldriver = real_xldriver
canlib.WaitForSingleObject = real_waitforsingleobject
+ canlib.HAS_EVENTS = real_has_events
def test_bus_creation_mocked(mock_xldriver) -> None:
@@ -870,13 +872,14 @@ def xlGetChannelIndex(
def xlOpenPort(
port_handle_p: ctypes.POINTER(xlclass.XLportHandle),
app_name_p: ctypes.c_char_p,
- access_mask: xlclass.XLaccess,
- permission_mask_p: ctypes.POINTER(xlclass.XLaccess),
+ access_mask: int,
+ permission_mask: xlclass.XLaccess,
rx_queue_size: ctypes.c_uint,
xl_interface_version: ctypes.c_uint,
bus_type: ctypes.c_uint,
) -> int:
port_handle_p.value = 0
+ permission_mask.value = access_mask
return 0
From 5f485dbeffaa4fd729dc2576625ec73bfda0011b Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Wed, 9 Nov 2022 12:08:04 -0500
Subject: [PATCH 158/475] Fixing memory leak in neoVI bus where
message_receipts grows (#1427)
message_receipts was growing without bound on msg Rx side
---
can/interfaces/ics_neovi/neovi_bus.py | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py
index 5366f8155..bd4fc5445 100644
--- a/can/interfaces/ics_neovi/neovi_bus.py
+++ b/can/interfaces/ics_neovi/neovi_bus.py
@@ -318,8 +318,9 @@ def _process_msg_queue(self, timeout=0.1):
if is_tx:
if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR):
continue
- if ics_msg.DescriptionID:
- receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID)
+
+ receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID)
+ if ics_msg.DescriptionID and receipt_key in self.message_receipts:
self.message_receipts[receipt_key].set()
if not self._receive_own_messages:
continue
@@ -477,11 +478,10 @@ def send(self, msg, timeout=0):
else:
raise ValueError("msg.channel must be set when using multiple channels.")
- msg_desc_id = next(description_id)
- message.DescriptionID = msg_desc_id
- receipt_key = (msg.arbitration_id, msg_desc_id)
-
if timeout != 0:
+ msg_desc_id = next(description_id)
+ message.DescriptionID = msg_desc_id
+ receipt_key = (msg.arbitration_id, msg_desc_id)
self.message_receipts[receipt_key].clear()
try:
@@ -492,5 +492,9 @@ def send(self, msg, timeout=0):
# If timeout is set, wait for ACK
# This requires a notifier for the bus or
# some other thread calling recv periodically
- if timeout != 0 and not self.message_receipts[receipt_key].wait(timeout):
- raise CanTimeoutError("Transmit timeout")
+ if timeout != 0:
+ got_receipt = self.message_receipts[receipt_key].wait(timeout)
+ # We no longer need this receipt, so no point keeping it in memory
+ del self.message_receipts[receipt_key]
+ if not got_receipt:
+ raise CanTimeoutError("Transmit timeout")
From f739bccf7ffd882c42f4409948bf60ccd8a7dc4a Mon Sep 17 00:00:00 2001
From: Jack Cook
Date: Sun, 13 Nov 2022 12:50:08 -0600
Subject: [PATCH 159/475] Add gzip check to compress method (#1429)
* Add gzip check to compress method
Addresses @zariiii9003 from https://github.com/hardbyte/python-can/pull/1385#issuecomment-1297372655
* Update can/io/logger.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update logger.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/logger.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/can/io/logger.py b/can/io/logger.py
index 09312101b..a254fb146 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -105,6 +105,10 @@ def compress(
File will automatically recompress upon close.
"""
real_suffix = pathlib.Path(filename).suffixes[-2].lower()
+ if real_suffix in (".blf", ".db"):
+ raise ValueError(
+ f"The file type {real_suffix} is currently incompatible with gzip."
+ )
if kwargs.get("append", False):
mode = "ab" if real_suffix == ".blf" else "at"
else:
From 8c4ed5ffe61617daafbc9418929a37aeadc989d8 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 14 Nov 2022 09:12:06 +0100
Subject: [PATCH 160/475] Update CHANGELOG for v4.1.0 release (#1363)
---
CHANGELOG.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
can/__init__.py | 2 +-
2 files changed, 75 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86612f9b9..1f10e2b78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,77 @@
+Version 4.1.0
+====
+
+Features
+--------
+
+### IO
+
+* The canutils logger preserves message direction (#1244)
+ and uses common interface names (e.g. can0) instead of just
+ channel numbers (#1271).
+* The ``can.logger`` script accepts the ``-a, --append`` option
+ to add new data to an existing log file (#1326, #1327, #1361).
+ Currently only the blf-, canutils- and csv-formats are supported.
+* All CLI ``extra_args`` are passed to the bus, logger
+ and player initialisation (#1366).
+
+### Type Annotations
+* python-can now includes the ``py.typed`` marker to support type checking
+ according to PEP 561 (#1344).
+
+### Interface Improvements
+* The gs_usb interface can be selected by device index instead
+ of USB bus/address. Loopback frames are now correctly marked
+ with the ``is_rx`` flag (#1270).
+* The PCAN interface can be selected by its device ID instead
+ of just the channel name (#1346).
+* The PCAN Bus implementation supports auto bus-off reset (#1345).
+* SocketCAN: Make ``find_available_interfaces()`` find slcanX interfaces (#1369).
+* Vector: Add xlGetReceiveQueueLevel, xlGenerateSyncPulse and
+ xlFlushReceiveQueue to xldriver (#1387).
+* Vector: Raise a CanInitializationError, if the CAN settings can not
+ be applied according to the arguments of ``VectorBus.__init__`` (#1426).
+
+Bug Fixes
+---------
+
+* Improve robustness of USB2CAN serial number detection (#1129).
+* Fix channel2int conversion (#1268, #1269).
+* Fix BLF timestamp conversion (#1266, #1273).
+* Fix timestamp handling in udp_multicast on macOS (#1275, #1278).
+* Fix failure to initiate the Neousys DLL (#1281).
+* Fix AttributeError in IscanError (#1292, #1293).
+* Add missing vector devices (#1296).
+* Fix error for DLC > 8 in ASCReader (#1299, #1301).
+* Set default mode for FileIOMessageWriter to wt instead of rt (#1303).
+* Fix conversion for port number from config file (#1309).
+* Fix fileno error on Windows (#1312, #1313, #1333).
+* Remove redundant ``writer.stop()`` call that throws error (#1316, #1317).
+* Detect and cast types of CLI ``extra_args`` (#1280, #1328).
+* Fix ASC/CANoe incompatibility due to timestamp format (#1315, #1362).
+* Fix MessageSync timings (#1372, #1374).
+* Fix file name for compressed files in SizedRotatingLogger (#1382, #1683).
+* Fix memory leak in neoVI bus where message_receipts grows with no limit (#1427).
+* Raise ValueError if gzip is used with incompatible log formats (#1429).
+
+Miscellaneous
+-------------
+
+* Allow ICSApiError to be pickled and un-pickled (#1341)
+* Sort interface names in CLI API to make documentation reproducible (#1342)
+* Exclude repository-configuration from git-archive (#1343)
+* Improve documentation (#1397, #1401, #1405, #1420, #1421)
+* Officially support Python 3.11 (#1423)
+
+Breaking Changes
+----------------
+
+* ``windows-curses`` was moved to optional dependencies (#1395).
+ Use ``pip install python-can[viewer]`` if you are using the ``can.viewer``
+ script on Windows.
+* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed
+ from camelCase to snake_case (#1422).
+
Version 4.0.0
====
diff --git a/can/__init__.py b/can/__init__.py
index 2a0b805ac..18d226867 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.0.0"
+__version__ = "4.1.0.dev0"
log = logging.getLogger("can")
From 4b1acdee8dbf6557f71911275106fff06287bb5b Mon Sep 17 00:00:00 2001
From: Giuseppe Corbelli
Date: Mon, 14 Nov 2022 10:04:53 +0100
Subject: [PATCH 161/475] Ixxat bus state and hardware errors detection (#1141)
* Added comment to CAN_MSGFLAGS_* and CAN_MSGFLAGS2_* constants
* CANMSGINFO.bAddFlags has been renamed to bFlags2 in IXXAT VCI4
* Added a comment that CANMSGINFO.Bytes.bAddFlags is called bFlags2 in VCI v4.
* Implementation is now tested against VCI v4
* Dropped manual timeout handling as it is a job done by BusABC.read().
Better handling of CAN error messages:
- In case of HW errors (overrun, warning limit exceeded, bus coupling error) raise VCIError
- In case of error log IXXAT-specific error codes
* Fixed unbound variable usage.
Use log.warning instead of deprecated log.warn.
* Now wrapping IXXAT VCI v4
* Added CAN_OPMODE_AUTOBAUD controller operating mode.
* Mapped symbol canChannelGetStatus, used to implement the 'state' property.
Hardware error checking in _recv_internal() handles BUS OFF situation.
* Use CANMSGINFO.bAddFlags instead of CANMSGINFO.bFlags2
* Call canControlClose() AFTER canControlReset() or it will always fail
* Added CANMSG.__str__
* Removed conflict marker
* Renamed parameter 'msg' to 'msgs' in _send_periodic_internal(), consistent with BusABC
* Changed plain format() calls to f-strings as per review
* Removed binascii module dependency using memoryview
Co-authored-by: Giuseppe Corbelli
---
can/interfaces/ixxat/__init__.py | 4 +-
can/interfaces/ixxat/canlib.py | 19 ++-
can/interfaces/ixxat/canlib_vcinpl.py | 205 +++++++++++++++----------
can/interfaces/ixxat/canlib_vcinpl2.py | 12 +-
can/interfaces/ixxat/constants.py | 30 ++--
can/interfaces/ixxat/exceptions.py | 2 +-
can/interfaces/ixxat/structures.py | 33 ++--
7 files changed, 183 insertions(+), 122 deletions(-)
diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py
index 347caed50..1419d97a1 100644
--- a/can/interfaces/ixxat/__init__.py
+++ b/can/interfaces/ixxat/__init__.py
@@ -1,7 +1,7 @@
"""
-Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
+Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
-Copyright (C) 2016 Giuseppe Corbelli
+Copyright (C) 2016-2021 Giuseppe Corbelli
"""
from can.interfaces.ixxat.canlib import IXXATBus
diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py
index 4dc0d3e6e..1e4055f89 100644
--- a/can/interfaces/ixxat/canlib.py
+++ b/can/interfaces/ixxat/canlib.py
@@ -2,6 +2,8 @@
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
from can import BusABC, Message
+from can.bus import BusState
+
from typing import Optional
@@ -11,7 +13,8 @@ class IXXATBus(BusABC):
Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN,
the other with CAN-FD.
- This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) class depending on fd user option.
+ This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2)
+ class depending on fd user option.
"""
def __init__(
@@ -140,8 +143,18 @@ def _recv_internal(self, timeout):
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
return self.bus.send(msg, timeout)
- def _send_periodic_internal(self, msg, period, duration=None):
- return self.bus._send_periodic_internal(msg, period, duration)
+ def _send_periodic_internal(self, msgs, period, duration=None):
+ return self.bus._send_periodic_internal(msgs, period, duration)
def shutdown(self):
return self.bus.shutdown()
+
+ @property
+ def state(self) -> BusState:
+ """
+ Return the current state of the hardware
+ """
+ return self.bus.state
+
+
+# ~class IXXATBus(BusABC): ---------------------------------------------------
diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py
index bdb05cda5..fa88e5f90 100644
--- a/can/interfaces/ixxat/canlib_vcinpl.py
+++ b/can/interfaces/ixxat/canlib_vcinpl.py
@@ -1,5 +1,5 @@
"""
-Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
+Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
TODO: We could implement this interface such that setting other filters
could work when the initial filters were set to zero using the
@@ -16,6 +16,7 @@
from typing import Optional, Callable, Tuple
from can import BusABC, Message
+from can.bus import BusState
from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError
from can.broadcastmanager import (
LimitedDurationCyclicSendTaskABC,
@@ -38,7 +39,6 @@
log = logging.getLogger("can.ixxat")
-from time import perf_counter
# Hack to have vciFormatError as a free function, see below
vciFormatError = None
@@ -225,6 +225,13 @@ def __check_status(result, function, args):
(HANDLE, ctypes.c_uint32, structures.PCANMSG),
__check_status,
)
+ # HRESULT canChannelGetStatus (HANDLE hCanChn, PCANCHANSTATUS pStatus );
+ _canlib.map_symbol(
+ "canChannelGetStatus",
+ ctypes.c_long,
+ (HANDLE, structures.PCANCHANSTATUS),
+ __check_status,
+ )
# EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl );
_canlib.map_symbol(
@@ -509,11 +516,11 @@ def __init__(
== bytes(unique_hardware_id, "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)
try:
@@ -522,7 +529,9 @@ def __init__(
ctypes.byref(self._device_handle),
)
except Exception as exception:
- raise CanInitializationError(f"Could not open device: {exception}")
+ raise CanInitializationError(
+ f"Could not open device: {exception}"
+ ) from exception
log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar)
@@ -543,7 +552,7 @@ def __init__(
except Exception as exception:
raise CanInitializationError(
f"Could not open and initialize channel: {exception}"
- )
+ ) from exception
# Signal TX/RX events when at least one frame has been handled
_canlib.canChannelInitialize(
@@ -637,85 +646,88 @@ def flush_tx_buffer(self):
def _recv_internal(self, timeout):
"""Read a message from IXXAT device."""
-
- # TODO: handling CAN error messages?
data_received = False
- if timeout == 0:
+ if self._inWaiting() or timeout == 0:
# Peek without waiting
- try:
- _canlib.canChannelPeekMessage(
- self._channel_handle, ctypes.byref(self._message)
- )
- except (VCITimeout, VCIRxQueueEmptyError):
- return None, True
- else:
- if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
- data_received = True
+ recv_function = functools.partial(
+ _canlib.canChannelPeekMessage,
+ self._channel_handle,
+ ctypes.byref(self._message),
+ )
else:
# Wait if no message available
- if timeout is None or timeout < 0:
- remaining_ms = constants.INFINITE
- t0 = None
- else:
- timeout_ms = int(timeout * 1000)
- remaining_ms = timeout_ms
- t0 = perf_counter()
-
- while True:
- try:
- _canlib.canChannelReadMessage(
- self._channel_handle, remaining_ms, ctypes.byref(self._message)
+ timeout = (
+ constants.INFINITE
+ if (timeout is None or timeout < 0)
+ else int(timeout * 1000)
+ )
+ recv_function = functools.partial(
+ _canlib.canChannelReadMessage,
+ self._channel_handle,
+ timeout,
+ ctypes.byref(self._message),
+ )
+
+ try:
+ recv_function()
+ except (VCITimeout, VCIRxQueueEmptyError):
+ # Ignore the 2 errors, overall timeout is handled by BusABC.recv
+ pass
+ else:
+ # See if we got a data or info/error messages
+ if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
+ data_received = True
+ elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
+ log.info(
+ CAN_INFO_MESSAGES.get(
+ self._message.abData[0],
+ f"Unknown CAN info message code {self._message.abData[0]}",
)
- except (VCITimeout, VCIRxQueueEmptyError):
- # Ignore the 2 errors, the timeout is handled manually with the perf_counter()
- pass
+ )
+ elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR:
+ if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR:
+ log.warning("CAN error: data overrun")
else:
- # See if we got a data or info/error messages
- if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
- data_received = True
- break
- elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
- log.info(
- CAN_INFO_MESSAGES.get(
- self._message.abData[0],
- "Unknown CAN info message code {}".format(
- self._message.abData[0]
- ),
- )
- )
-
- elif (
- self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR
- ):
- log.warning(
- CAN_ERROR_MESSAGES.get(
- self._message.abData[0],
- "Unknown CAN error message code {}".format(
- self._message.abData[0]
- ),
- )
+ log.warning(
+ CAN_ERROR_MESSAGES.get(
+ self._message.abData[0],
+ f"Unknown CAN error message code {self._message.abData[0]}",
)
-
- elif (
- self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS
- ):
- log.info(_format_can_status(self._message.abData[0]))
- if self._message.abData[0] & constants.CAN_STATUS_BUSOFF:
- raise VCIBusOffError()
-
- elif (
- self._message.uMsgInfo.Bits.type
- == constants.CAN_MSGTYPE_TIMEOVR
- ):
- pass
- else:
- log.warning("Unexpected message info type")
-
- if t0 is not None:
- remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000)
- if remaining_ms < 0:
- break
+ )
+ log.warning(
+ "CAN message flags bAddFlags/bFlags2 0x%02X bflags 0x%02X",
+ self._message.uMsgInfo.Bytes.bAddFlags,
+ self._message.uMsgInfo.Bytes.bFlags,
+ )
+ elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR:
+ pass
+ else:
+ log.warning(
+ "Unexpected message info type 0x%X",
+ self._message.uMsgInfo.Bits.type,
+ )
+ finally:
+ if not data_received:
+ # Check hard errors
+ status = structures.CANLINESTATUS()
+ _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
+ error_byte_1 = status.dwStatus & 0x0F
+ error_byte_2 = status.dwStatus & 0xF0
+ if error_byte_1 > constants.CAN_STATUS_TXPEND:
+ # CAN_STATUS_OVRRUN = 0x02 # data overrun occurred
+ # CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded
+ # CAN_STATUS_BUSOFF = 0x08 # bus off status
+ if error_byte_1 & constants.CAN_STATUS_OVRRUN:
+ raise VCIError("Data overrun occurred")
+ elif error_byte_1 & constants.CAN_STATUS_ERRLIM:
+ raise VCIError("Error warning limit exceeded")
+ elif error_byte_1 & constants.CAN_STATUS_BUSOFF:
+ raise VCIError("Bus off status")
+ elif error_byte_2 > constants.CAN_STATUS_ININIT:
+ # CAN_STATUS_BUSCERR = 0x20 # bus coupling error
+ if error_byte_2 & constants.CAN_STATUS_BUSCERR:
+ raise VCIError("Bus coupling error")
if not data_received:
# Timed out / can message type is not DATA
@@ -764,11 +776,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
_canlib.canChannelSendMessage(
self._channel_handle, int(timeout * 1000), message
)
-
else:
_canlib.canChannelPostMessage(self._channel_handle, message)
+ # Want to log outgoing messages?
+ # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message)
- def _send_periodic_internal(self, msg, period, duration=None):
+ def _send_periodic_internal(self, msgs, period, duration=None):
"""Send a message using built-in cyclic transmit list functionality."""
if self._scheduler is None:
self._scheduler = HANDLE()
@@ -778,7 +791,7 @@ def _send_periodic_internal(self, msg, period, duration=None):
self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
return CyclicSendTask(
- self._scheduler, msg, period, duration, self._scheduler_resolution
+ self._scheduler, msgs, period, duration, self._scheduler_resolution
)
def shutdown(self):
@@ -786,9 +799,35 @@ def shutdown(self):
_canlib.canSchedulerClose(self._scheduler)
_canlib.canChannelClose(self._channel_handle)
_canlib.canControlStart(self._control_handle, constants.FALSE)
+ _canlib.canControlReset(self._control_handle)
_canlib.canControlClose(self._control_handle)
_canlib.vciDeviceClose(self._device_handle)
+ @property
+ def state(self) -> BusState:
+ """
+ Return the current state of the hardware
+ """
+ status = structures.CANLINESTATUS()
+ _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
+ if status.bOpMode == constants.CAN_OPMODE_LISTONLY:
+ return BusState.PASSIVE
+
+ error_byte_1 = status.dwStatus & 0x0F
+ # CAN_STATUS_BUSOFF = 0x08 # bus off status
+ if error_byte_1 & constants.CAN_STATUS_BUSOFF:
+ return BusState.ERROR
+
+ error_byte_2 = status.dwStatus & 0xF0
+ # CAN_STATUS_BUSCERR = 0x20 # bus coupling error
+ if error_byte_2 & constants.CAN_STATUS_BUSCERR:
+ raise BusState.ERROR
+
+ return BusState.ACTIVE
+
+
+# ~class IXXATBus(BusABC): ---------------------------------------------------
+
class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC):
"""A message in the cyclic transmit list."""
diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py
index 802168630..108ad2c02 100644
--- a/can/interfaces/ixxat/canlib_vcinpl2.py
+++ b/can/interfaces/ixxat/canlib_vcinpl2.py
@@ -825,9 +825,7 @@ def _recv_internal(self, timeout):
log.info(
CAN_INFO_MESSAGES.get(
self._message.abData[0],
- "Unknown CAN info message code {}".format(
- self._message.abData[0]
- ),
+ f"Unknown CAN info message code {self._message.abData[0]}",
)
)
@@ -837,9 +835,7 @@ def _recv_internal(self, timeout):
log.warning(
CAN_ERROR_MESSAGES.get(
self._message.abData[0],
- "Unknown CAN error message code {}".format(
- self._message.abData[0]
- ),
+ f"Unknown CAN error message code {self._message.abData[0]}",
)
)
@@ -933,7 +929,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
else:
_canlib.canChannelPostMessage(self._channel_handle, message)
- def _send_periodic_internal(self, msg, period, duration=None):
+ def _send_periodic_internal(self, msgs, period, duration=None):
"""Send a message using built-in cyclic transmit list functionality."""
if self._scheduler is None:
self._scheduler = HANDLE()
@@ -945,7 +941,7 @@ def _send_periodic_internal(self, msg, period, duration=None):
) # TODO: confirm
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
return CyclicSendTask(
- self._scheduler, msg, period, duration, self._scheduler_resolution
+ self._scheduler, msgs, period, duration, self._scheduler_resolution
)
def shutdown(self):
diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py
index 1dbc22a44..3bc1aa42e 100644
--- a/can/interfaces/ixxat/constants.py
+++ b/can/interfaces/ixxat/constants.py
@@ -1,5 +1,5 @@
"""
-Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
+Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
"""
@@ -106,20 +106,21 @@
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 # transmission pending
+CAN_STATUS_OVRRUN = 0x02 # data overrun occurred
+CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded
+CAN_STATUS_BUSOFF = 0x08 # bus off status
+CAN_STATUS_ININIT = 0x10 # init mode active
+CAN_STATUS_BUSCERR = 0x20 # bus coupling error
# Controller operating modes
-CAN_OPMODE_UNDEFINED = 0x00
-CAN_OPMODE_STANDARD = 0x01
-CAN_OPMODE_EXTENDED = 0x02
-CAN_OPMODE_ERRFRAME = 0x04
-CAN_OPMODE_LISTONLY = 0x08
-CAN_OPMODE_LOWSPEED = 0x10
+CAN_OPMODE_UNDEFINED = 0x00 # undefined
+CAN_OPMODE_STANDARD = 0x01 # reception of 11-bit id messages
+CAN_OPMODE_EXTENDED = 0x02 # reception of 29-bit id messages
+CAN_OPMODE_ERRFRAME = 0x04 # reception of error frames
+CAN_OPMODE_LISTONLY = 0x08 # listen only mode (TX passive)
+CAN_OPMODE_LOWSPEED = 0x10 # use low speed bus interface
+CAN_OPMODE_AUTOBAUD = 0x20 # automatic bit rate detection
# Extended operating modes
CAN_EXMODE_DISABLED = 0x00
@@ -167,13 +168,14 @@
CAN_FILTER_EXCL = 0x04 # exclusive filtering (inhibit registered IDs)
+# message information flags (used by )
CAN_MSGFLAGS_DLC = 0x0F # [bit 0] data length code
CAN_MSGFLAGS_OVR = 0x10 # [bit 4] data overrun flag
CAN_MSGFLAGS_SRR = 0x20 # [bit 5] self reception request
CAN_MSGFLAGS_RTR = 0x40 # [bit 6] remote transmission request
CAN_MSGFLAGS_EXT = 0x80 # [bit 7] frame format (0=11-bit, 1=29-bit)
-
+# extended message information flags (used by )
CAN_MSGFLAGS2_SSM = 0x01 # [bit 0] single shot mode
CAN_MSGFLAGS2_HPM = 0x02 # [bit 1] high priority message
CAN_MSGFLAGS2_EDL = 0x04 # [bit 2] extended data length
diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py
index babe08e3b..50b84dfa4 100644
--- a/can/interfaces/ixxat/exceptions.py
+++ b/can/interfaces/ixxat/exceptions.py
@@ -1,5 +1,5 @@
"""
-Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
+Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
Copyright (C) 2019 Marcel Kanter
diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py
index f76a39a38..b784437e0 100644
--- a/can/interfaces/ixxat/structures.py
+++ b/can/interfaces/ixxat/structures.py
@@ -1,5 +1,5 @@
"""
-Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
+Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
"""
@@ -70,11 +70,13 @@ def __str__(self):
class CANLINESTATUS(ctypes.Structure):
_fields_ = [
+ # current CAN operating mode. Value is a logical combination of
+ # one or more CAN_OPMODE_xxx constants
("bOpMode", ctypes.c_uint8),
- ("bBtReg0", ctypes.c_uint8),
- ("bBtReg1", ctypes.c_uint8),
- ("bBusLoad", ctypes.c_uint8),
- ("dwStatus", ctypes.c_uint32),
+ ("bBtReg0", ctypes.c_uint8), # current bus timing register 0 value
+ ("bBtReg1", ctypes.c_uint8), # current bus timing register 1 value
+ ("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100)
+ ("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_)
]
@@ -83,11 +85,11 @@ class CANLINESTATUS(ctypes.Structure):
class CANCHANSTATUS(ctypes.Structure):
_fields_ = [
- ("sLineStatus", CANLINESTATUS),
- ("fActivated", ctypes.c_uint32),
- ("fRxOverrun", ctypes.c_uint32),
- ("bRxFifoLoad", ctypes.c_uint8),
- ("bTxFifoLoad", ctypes.c_uint8),
+ ("sLineStatus", CANLINESTATUS), # current CAN line status
+ ("fActivated", ctypes.c_uint32), # TRUE if the channel is activated
+ ("fRxOverrun", ctypes.c_uint32), # TRUE if receive FIFO overrun occurred
+ ("bRxFifoLoad", ctypes.c_uint8), # receive FIFO load in percent (0..100)
+ ("bTxFifoLoad", ctypes.c_uint8), # transmit FIFO load in percent (0..100)
]
@@ -118,7 +120,7 @@ class Bytes(ctypes.Structure):
(
"bAddFlags",
ctypes.c_uint8,
- ), # extended flags (see CAN_MSGFLAGS2_ constants)
+ ), # extended flags (see CAN_MSGFLAGS2_ constants). AKA bFlags2 in VCI v4
("bFlags", ctypes.c_uint8), # flags (see CAN_MSGFLAGS_ constants)
("bAccept", ctypes.c_uint8), # accept code (see CAN_ACCEPT_ constants)
]
@@ -153,11 +155,20 @@ class Bits(ctypes.Structure):
class CANMSG(ctypes.Structure):
_fields_ = [
("dwTime", ctypes.c_uint32),
+ # CAN ID of the message in Intel format (aligned right) without RTR bit.
("dwMsgId", ctypes.c_uint32),
("uMsgInfo", CANMSGINFO),
("abData", ctypes.c_uint8 * 8),
]
+ def __str__(self) -> str:
+ return """ID: 0x{0:04x}{1} DLC: {2:02d} DATA: {3}""".format(
+ self.dwMsgId,
+ "[RTR]" if self.uMsgInfo.Bits.rtr else "",
+ self.uMsgInfo.Bits.dlc,
+ memoryview(self.abData)[: self.uMsgInfo.Bits.dlc].hex(sep=" "),
+ )
+
PCANMSG = ctypes.POINTER(CANMSG)
From b0a44001c68d6e30c3b308f558c78685de8460bd Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Tue, 15 Nov 2022 18:47:43 +1300
Subject: [PATCH 162/475] Switch from codecov to coveralls (#1430)
---
.github/workflows/build.yml | 20 +++++++++++++++++---
README.rst | 6 +++---
doc/development.rst | 2 +-
test/test_message_class.py | 5 +++--
tox.ini | 12 +++++-------
5 files changed, 29 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 39eae343f..2ed1742c6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -37,10 +37,24 @@ jobs:
- name: Test with pytest via tox
run: |
tox -e gh
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v3
+
+ - name: Coveralls Parallel
+ uses: coverallsapp/github-action@master
+ with:
+ github-token: ${{ secrets.github_token }}
+ flag-name: Unittests-${{ matrix.os }}-${{ matrix.python-version }}
+ parallel: true
+ path-to-lcov: ./coverage.lcov
+
+ coveralls:
+ needs: test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Coveralls Finished
+ uses: coverallsapp/github-action@master
with:
- fail_ci_if_error: true
+ github-token: ${{ secrets.github_token }}
+ parallel-finished: true
static-code-analysis:
runs-on: ubuntu-latest
diff --git a/README.rst b/README.rst
index 0e5d79e3f..11404785a 100644
--- a/README.rst
+++ b/README.rst
@@ -37,9 +37,9 @@ python-can
:target: https://app.travis-ci.com/github/hardbyte/python-can
:alt: Travis CI Server for develop branch
-.. |coverage| image:: https://codecov.io/gh/hardbyte/python-can/branch/develop/graph/badge.svg
- :target: https://codecov.io/gh/hardbyte/python-can/branch/develop
- :alt: Test coverage reports on Codecov.io
+.. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=develop
+ :target: https://coveralls.io/github/hardbyte/python-can?branch=develop
+ :alt: Test coverage reports on Coveralls.io
.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat
:target: https://mergify.io
diff --git a/doc/development.rst b/doc/development.rst
index 055401bdc..cfb8dbe5d 100644
--- a/doc/development.rst
+++ b/doc/development.rst
@@ -108,7 +108,7 @@ The modules in ``python-can`` are:
Creating a new Release
----------------------
-- Release from the ``master`` branch (except for pre-releases).
+- Release from the ``main`` branch (except for pre-releases).
- Update the library version in ``__init__.py`` using `semantic versioning `__.
- Check if any deprecations are pending.
- Run all tests and examples against available hardware.
diff --git a/test/test_message_class.py b/test/test_message_class.py
index 688cda24f..9fae7262e 100644
--- a/test/test_message_class.py
+++ b/test/test_message_class.py
@@ -7,7 +7,7 @@
import pickle
from datetime import timedelta
-from hypothesis import given, settings
+from hypothesis import HealthCheck, given, settings
import hypothesis.errors
import hypothesis.strategies as st
@@ -42,12 +42,13 @@ class TestMessageClass(unittest.TestCase):
# The first run may take a second on CI runners and will hit the deadline
@settings(
max_examples=2000,
+ suppress_health_check=[HealthCheck.too_slow],
deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500),
)
@pytest.mark.xfail(
IS_WINDOWS and IS_PYPY,
raises=hypothesis.errors.Flaky,
- reason="Hypothesis generates inconistent timestamp floats on Windows+PyPy-3.7",
+ reason="Hypothesis generates inconsistent timestamp floats on Windows+PyPy-3.7",
)
def test_methods(self, **kwargs):
is_valid = not (
diff --git a/tox.ini b/tox.ini
index 248a6fd37..0dbd6423e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,9 +5,9 @@ isolated_build = true
deps =
pytest==7.1.*,>=7.1.2
pytest-timeout==2.0.2
- pytest-cov==3.0.0
- coverage==6.3
- codecov==2.1.12
+ coveralls==3.3.1
+ pytest-cov==4.0.0
+ coverage==6.5.0
hypothesis~=6.35.0
pyserial~=3.5
parameterized~=0.8
@@ -24,6 +24,7 @@ recreate = True
passenv =
CI
GITHUB_*
+ COVERALLS_*
PY_COLORS
[testenv:travis]
@@ -31,15 +32,12 @@ passenv =
CI
TRAVIS
TRAVIS_*
- CODECOV_*
TEST_SOCKETCAN
-commands_post =
- codecov -X gcov
[pytest]
testpaths = test
-addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=xml --cov-report=term
+addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=lcov --cov-report=term
[coverage:run]
From 2d6e996a7efda5fbf7da51642422ca49b315dfa1 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Tue, 15 Nov 2022 07:20:25 +0100
Subject: [PATCH 163/475] Trc file support (#1217)
* Added file for implementation of trc file read and write
* Added trc file header template
* Added link where to lookup the trc file format description
* Add trc reader and writer to backends
* Add example trace file as conversion from asc log
* Add TRCWriter and TRCReader to can package
* Basic header extraction in reader
* Implement read of file version
* Remove useless format string
* Implement basic message parsing
* Move message parsing to separate function
* Implement parse of first message
* Handle none rx messages
* Handle empty files
* Add test method for trc files
* Format code with black
* Implement stop at end of log file
* Implement basic write of header version 2.1
* Add newline after header
* Implement write of messages
* Enable test for trace file writing
* Add test files for different pcan trace file versions
* Use binary mode for write to ensure correct line ending on all platforms
* Add handler for file write
* Move some header lines to specific write function
* Move lines to function
* Move write of file header to class
* Use new function for better code readability
* Add enum for file versions
* Handle text io streams correctly
* send line ending setting to logger
* Add check for file version in writer
* Add TRCFileVersion as export
* Add test for wrong file version
* Add format and header for version 1 trace file format
* Use correct format according to selected version
* Introduce handler method _parse_line
* Skip empty lines
* Add check for type before eval message
* Print info on unsupported types
* Add test for new test data files trc format
* Implement file version reading
* More flexible implementation of line parsing for different versions
* Implement Version 1 trace file parsing
* Add test case for Version 1.0 trc file
* Implement parsing for version 1.1 trace files
* Add test for version 1.1 reading
* Add test case for version 1.0 trace files
* Avoid multi test runs with same input and output by separate generic tests from file version tests
* Correct first timestamp
* Add type information for file attribute
* Add type definitions for init function
* Add type for first_timestamp
* Add info for return types
* Drop type casting for file. Should already be done in init
* Add type info
* Always use text read write
* Update types for initialization
* Use text io mode by default
---
can/__init__.py | 1 +
can/io/__init__.py | 1 +
can/io/logger.py | 3 +
can/io/player.py | 3 +
can/io/trc.py | 368 ++++++++++++++++++++++++
test/data/test_CanMessage.trc | 23 ++
test/data/test_CanMessage_V1_0_BUS1.trc | 28 ++
test/data/test_CanMessage_V1_1.trc | 25 ++
test/data/test_CanMessage_V2_0_BUS1.trc | 28 ++
test/data/test_CanMessage_V2_1.trc | 29 ++
test/logformats_test.py | 128 +++++++++
11 files changed, 637 insertions(+)
create mode 100644 can/io/trc.py
create mode 100644 test/data/test_CanMessage.trc
create mode 100644 test/data/test_CanMessage_V1_0_BUS1.trc
create mode 100644 test/data/test_CanMessage_V1_1.trc
create mode 100644 test/data/test_CanMessage_V2_0_BUS1.trc
create mode 100644 test/data/test_CanMessage_V2_1.trc
diff --git a/can/__init__.py b/can/__init__.py
index 18d226867..8af42009a 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -41,6 +41,7 @@
from .io import CanutilsLogReader, CanutilsLogWriter
from .io import CSVWriter, CSVReader
from .io import SqliteWriter, SqliteReader
+from .io import TRCReader, TRCWriter, TRCFileVersion
from .broadcastmanager import (
CyclicSendTaskABC,
diff --git a/can/io/__init__.py b/can/io/__init__.py
index 0d3741b05..6dc9ac1af 100644
--- a/can/io/__init__.py
+++ b/can/io/__init__.py
@@ -14,3 +14,4 @@
from .csv import CSVWriter, CSVReader
from .sqlite import SqliteReader, SqliteWriter
from .printer import Printer
+from .trc import TRCReader, TRCWriter, TRCFileVersion
diff --git a/can/io/logger.py b/can/io/logger.py
index a254fb146..a08cf9869 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -23,6 +23,7 @@
from .csv import CSVWriter
from .sqlite import SqliteWriter
from .printer import Printer
+from .trc import TRCWriter
from ..typechecking import StringPathLike, FileLike, AcceptedIOType
@@ -36,6 +37,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method
* .csv: :class:`can.CSVWriter`
* .db: :class:`can.SqliteWriter`
* .log :class:`can.CanutilsLogWriter`
+ * .trc :class:`can.TRCWriter`
* .txt :class:`can.Printer`
Any of these formats can be used with gzip compression by appending
@@ -58,6 +60,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method
".csv": CSVWriter,
".db": SqliteWriter,
".log": CanutilsLogWriter,
+ ".trc": TRCWriter,
".txt": Printer,
}
diff --git a/can/io/player.py b/can/io/player.py
index 8eb4ba24f..82f851502 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -16,6 +16,7 @@
from .canutils import CanutilsLogReader
from .csv import CSVReader
from .sqlite import SqliteReader
+from .trc import TRCReader
from ..typechecking import StringPathLike, FileLike, AcceptedIOType
from ..message import Message
@@ -30,6 +31,7 @@ class LogReader(MessageReader):
* .csv
* .db
* .log
+ * .trc
Gzip compressed files can be used as long as the original
files suffix is one of the above (e.g. filename.asc.gz).
@@ -56,6 +58,7 @@ class LogReader(MessageReader):
".csv": CSVReader,
".db": SqliteReader,
".log": CanutilsLogReader,
+ ".trc": TRCReader,
}
@staticmethod
diff --git a/can/io/trc.py b/can/io/trc.py
new file mode 100644
index 000000000..25ba0f5c6
--- /dev/null
+++ b/can/io/trc.py
@@ -0,0 +1,368 @@
+# coding: utf-8
+
+"""
+Reader and writer for can logging files in peak trc format
+
+See https://www.peak-system.com/produktcd/Pdf/English/PEAK_CAN_TRC_File_Format.pdf
+for file format description
+
+Version 1.1 will be implemented as it is most commonly used
+""" # noqa
+
+from typing import Generator, Optional, Union, TextIO
+from datetime import datetime, timedelta
+from enum import Enum
+from io import TextIOWrapper
+import os
+import logging
+
+from ..message import Message
+from ..util import channel2int
+from .generic import FileIOMessageWriter, MessageReader
+from ..typechecking import StringPathLike
+
+
+logger = logging.getLogger("can.io.trc")
+
+
+class TRCFileVersion(Enum):
+ UNKNOWN = 0
+ V1_0 = 100
+ V1_1 = 101
+ V1_2 = 102
+ V1_3 = 103
+ V2_0 = 200
+ V2_1 = 201
+
+
+class TRCReader(MessageReader):
+ """
+ Iterator of CAN messages from a TRC logging file.
+ """
+
+ file: TextIO
+
+ def __init__(
+ self,
+ file: Union[StringPathLike, TextIO],
+ ) -> None:
+ """
+ :param file: a path-like object or as file-like object to read from
+ If this is a file-like object, is has to opened in text
+ read mode, not binary read mode.
+ """
+ super(TRCReader, self).__init__(file, mode="r")
+ self.file_version = TRCFileVersion.UNKNOWN
+
+ if not self.file:
+ raise ValueError("The given file cannot be None")
+
+ def _extract_header(self):
+ for line in self.file:
+ line = line.strip()
+ if line.startswith(";$FILEVERSION"):
+ logger.debug(f"TRCReader: Found file version '{line}'")
+ try:
+ file_version = line.split("=")[1]
+ if file_version == "1.1":
+ self.file_version = TRCFileVersion.V1_1
+ elif file_version == "2.1":
+ self.file_version = TRCFileVersion.V2_1
+ else:
+ self.file_version = TRCFileVersion.UNKNOWN
+ except IndexError:
+ logger.debug("TRCReader: Failed to parse version")
+ elif line.startswith(";"):
+ continue
+ else:
+ break
+
+ if self.file_version == TRCFileVersion.UNKNOWN:
+ logger.info(
+ "TRCReader: No file version was found, so version 1.0 is assumed"
+ )
+ self._parse_cols = self._parse_msg_V1_0
+ elif self.file_version == TRCFileVersion.V1_0:
+ self._parse_cols = self._parse_msg_V1_0
+ elif self.file_version == TRCFileVersion.V1_1:
+ self._parse_cols = self._parse_cols_V1_1
+ elif self.file_version == TRCFileVersion.V2_1:
+ self._parse_cols = self._parse_cols_V2_1
+ else:
+ raise NotImplementedError("File version not fully implemented for reading")
+
+ return line
+
+ def _parse_msg_V1_0(self, cols):
+ arbit_id = cols[2]
+ if arbit_id == "FFFFFFFF":
+ logger.info("TRCReader: Dropping bus info line")
+ return None
+
+ msg = Message()
+ msg.timestamp = float(cols[1]) / 1000
+ msg.arbitration_id = int(arbit_id, 16)
+ msg.is_extended_id = len(arbit_id) > 4
+ msg.channel = 1
+ msg.dlc = int(cols[3])
+ msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)])
+ return msg
+
+ def _parse_msg_V1_1(self, cols):
+ arbit_id = cols[3]
+
+ msg = Message()
+ msg.timestamp = float(cols[1]) / 1000
+ msg.arbitration_id = int(arbit_id, 16)
+ msg.is_extended_id = len(arbit_id) > 4
+ msg.channel = 1
+ msg.dlc = int(cols[4])
+ msg.data = bytearray([int(cols[i + 5], 16) for i in range(msg.dlc)])
+ msg.is_rx = cols[2] == "Rx"
+ return msg
+
+ def _parse_msg_V2_1(self, cols):
+ msg = Message()
+ msg.timestamp = float(cols[1]) / 1000
+ msg.arbitration_id = int(cols[4], 16)
+ msg.is_extended_id = len(cols[4]) > 4
+ msg.channel = int(cols[3])
+ msg.dlc = int(cols[7])
+ msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)])
+ msg.is_rx = cols[5] == "Rx"
+ return msg
+
+ def _parse_cols_V1_1(self, cols):
+ dtype = cols[2]
+ if dtype == "Tx" or dtype == "Rx":
+ return self._parse_msg_V1_1(cols)
+ else:
+ logger.info(f"TRCReader: Unsupported type '{dtype}'")
+ return None
+
+ def _parse_cols_V2_1(self, cols):
+ dtype = cols[2]
+ if dtype == "DT":
+ return self._parse_msg_V2_1(cols)
+ else:
+ logger.info(f"TRCReader: Unsupported type '{dtype}'")
+ return None
+
+ def _parse_line(self, line):
+ logger.debug(f"TRCReader: Parse '{line}'")
+ try:
+ cols = line.split()
+ return self._parse_cols(cols)
+ except IndexError:
+ logger.warning(f"TRCReader: Failed to parse message '{line}'")
+ return None
+
+ def __iter__(self) -> Generator[Message, None, None]:
+ first_line = self._extract_header()
+
+ if first_line is not None:
+ msg = self._parse_line(first_line)
+ if msg is not None:
+ yield msg
+
+ for line in self.file:
+ temp = line.strip()
+ if temp.startswith(";"):
+ # Comment line
+ continue
+
+ if len(temp) == 0:
+ # Empty line
+ continue
+
+ msg = self._parse_line(temp)
+ if msg is not None:
+ yield msg
+
+ self.stop()
+
+
+class TRCWriter(FileIOMessageWriter):
+ """Logs CAN data to text file (.trc).
+
+ The measurement starts with the timestamp of the first registered message.
+ If a message has a timestamp smaller than the previous one or None,
+ it gets assigned the timestamp that was written for the last message.
+ If the first message does not have a timestamp, it is set to zero.
+ """
+
+ file: TextIO
+ first_timestamp: Optional[float]
+
+ FORMAT_MESSAGE = (
+ "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}"
+ )
+ FORMAT_MESSAGE_V1_0 = "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}"
+
+ def __init__(
+ self,
+ file: Union[StringPathLike, TextIO],
+ channel: int = 1,
+ ) -> None:
+ """
+ :param file: a path-like object or as file-like object to write to
+ If this is a file-like object, is has to opened in text
+ write mode, not binary write mode.
+ :param channel: a default channel to use when the message does not
+ have a channel set
+ """
+ super(TRCWriter, self).__init__(file, mode="w")
+ self.channel = channel
+ if type(file) is str:
+ self.filepath = os.path.abspath(file)
+ elif type(file) is TextIOWrapper:
+ self.filepath = "Unknown"
+ logger.warning("TRCWriter: Text mode io can result in wrong line endings")
+ logger.debug(
+ f"TRCWriter: Text mode io line ending setting: {file.newlines}"
+ )
+ else:
+ self.filepath = "Unknown"
+
+ self.header_written = False
+ self.msgnr = 0
+ self.first_timestamp = None
+ self.file_version = TRCFileVersion.V2_1
+ self._format_message = self._format_message_init
+
+ def _write_line(self, line: str) -> None:
+ self.file.write(line + "\r\n")
+
+ def _write_lines(self, lines: list) -> None:
+ for line in lines:
+ self._write_line(line)
+
+ def _write_header_V1_0(self, start_time: timedelta) -> None:
+ self._write_line(
+ ";##########################################################################"
+ )
+ self._write_line(f"; {self.filepath}")
+ self._write_line(";")
+ self._write_line("; Generated by python-can TRCWriter")
+ self._write_line(f"; Start time: {start_time}")
+ self._write_line("; PCAN-Net: N/A")
+ self._write_line(";")
+ self._write_line("; Columns description:")
+ self._write_line("; ~~~~~~~~~~~~~~~~~~~~~")
+ self._write_line("; +-current number in actual sample")
+ self._write_line("; | +time offset of message (ms)")
+ self._write_line("; | | +ID of message (hex)")
+ self._write_line("; | | | +data length code")
+ self._write_line("; | | | | +data bytes (hex) ...")
+ self._write_line("; | | | | |")
+ self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...")
+
+ def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None:
+ milliseconds = int(
+ (header_time.seconds * 1000) + (header_time.microseconds / 1000)
+ )
+
+ self._write_line(";$FILEVERSION=2.1")
+ self._write_line(f";$STARTTIME={header_time.days}.{milliseconds}")
+ self._write_line(";$COLUMNS=N,O,T,B,I,d,R,L,D")
+ self._write_line(";")
+ self._write_line(f"; {self.filepath}")
+ self._write_line(";")
+ self._write_line(f"; Start time: {start_time}")
+ self._write_line("; Generated by python-can TRCWriter")
+ self._write_line(
+ ";-------------------------------------------------------------------------------"
+ )
+ self._write_line("; Bus Name Connection Protocol")
+ self._write_line("; N/A N/A N/A N/A")
+ self._write_line(
+ ";-------------------------------------------------------------------------------"
+ )
+ self._write_lines(
+ [
+ "; Message Time Type ID Rx/Tx",
+ "; Number Offset | Bus [hex] | Reserved",
+ "; | [ms] | | | | | Data Length Code",
+ "; | | | | | | | | Data [hex] ...",
+ "; | | | | | | | | |",
+ ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --",
+ ]
+ )
+
+ def _format_message_by_format(self, msg, channel):
+ if msg.is_extended_id:
+ arb_id = f"{msg.arbitration_id:07X}"
+ else:
+ arb_id = f"{msg.arbitration_id:04X}"
+
+ data = [f"{byte:02X}" for byte in msg.data]
+
+ serialized = self._msg_fmt_string.format(
+ msgnr=self.msgnr,
+ time=(msg.timestamp - self.first_timestamp) * 1000,
+ channel=channel,
+ id=arb_id,
+ dir="Rx" if msg.is_rx else "Tx",
+ dlc=msg.dlc,
+ data=" ".join(data),
+ )
+ return serialized
+
+ def _format_message_init(self, msg, channel):
+ if self.file_version == TRCFileVersion.V1_0:
+ self._format_message = self._format_message_by_format
+ self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0
+ elif self.file_version == TRCFileVersion.V2_1:
+ self._format_message = self._format_message_by_format
+ self._msg_fmt_string = self.FORMAT_MESSAGE
+ else:
+ raise NotImplementedError("File format is not supported")
+
+ return self._format_message(msg, channel)
+
+ def write_header(self, timestamp: float) -> None:
+ # write start of file header
+ ref_time = datetime(year=1899, month=12, day=30)
+ start_time = datetime.now() + timedelta(seconds=timestamp)
+ header_time = start_time - ref_time
+
+ if self.file_version == TRCFileVersion.V1_0:
+ self._write_header_V1_0(header_time)
+ elif self.file_version == TRCFileVersion.V2_1:
+ self._write_header_V2_1(header_time, start_time)
+ else:
+ raise NotImplementedError("File format is not supported")
+ self.header_written = True
+
+ def log_event(self, message: str, timestamp: float) -> None:
+ if not self.header_written:
+ self.write_header(timestamp)
+
+ self._write_line(message)
+
+ def on_message_received(self, msg: Message) -> None:
+ if self.first_timestamp is None:
+ self.first_timestamp = msg.timestamp
+
+ if msg.is_error_frame:
+ logger.warning("TRCWriter: Logging error frames is not implemented")
+ return
+
+ if msg.is_remote_frame:
+ logger.warning("TRCWriter: Logging remote frames is not implemented")
+ return
+
+ channel = channel2int(msg.channel)
+ if channel is None:
+ channel = self.channel
+ else:
+ # Many interfaces start channel numbering at 0 which is invalid
+ channel += 1
+
+ if msg.is_fd:
+ logger.warning("TRCWriter: Logging CAN FD is not implemented")
+ return
+ else:
+ serialized = self._format_message(msg, channel)
+ self.msgnr += 1
+ self.log_event(serialized, msg.timestamp)
diff --git a/test/data/test_CanMessage.trc b/test/data/test_CanMessage.trc
new file mode 100644
index 000000000..215997b57
--- /dev/null
+++ b/test/data/test_CanMessage.trc
@@ -0,0 +1,23 @@
+;$FILEVERSION=2.1
+;$STARTTIME=0
+;$COLUMNS=N,O,T,B,I,d,R,L,D
+;
+; C:\Users\User\Desktop\python-can\test\data\test_CanMessage.trc
+; Start time: 30.09.2017 22:06:13.191.000
+; Generated by PEAK-Converter Version 2.2.4.136
+; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage.asc
+;-------------------------------------------------------------------------------
+; Bus Name Connection Protocol
+; N/A N/A N/A N/A
+;-------------------------------------------------------------------------------
+; Message Time Type ID Rx/Tx
+; Number Offset | Bus [hex] | Reserved
+; | [ms] | | | | | Data Length Code
+; | | | | | | | | Data [hex] ...
+; | | | | | | | | |
+;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --
+;Begin Triggerblock Sat Sep 30 10:06:13.191 PM 2017
+; 0.000000 Start of measurement
+ 1 2501.000 DT 2 00C8 Tx - 8 09 08 07 06 05 04 03 02
+ 2 17876.708 DT 1 06F9 Rx - 8 05 0C 00 00 00 00 00 00
+;End TriggerBlock
diff --git a/test/data/test_CanMessage_V1_0_BUS1.trc b/test/data/test_CanMessage_V1_0_BUS1.trc
new file mode 100644
index 000000000..8985db188
--- /dev/null
+++ b/test/data/test_CanMessage_V1_0_BUS1.trc
@@ -0,0 +1,28 @@
+;##########################################################################
+; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_0_BUS1.trc
+;
+; CAN activities imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc
+; Start time: 18.12.2021 14:28:07.062
+; PCAN-Net: N/A
+; Generated by PEAK-Converter Version 2.2.4.136
+;
+; Columns description:
+; ~~~~~~~~~~~~~~~~~~~~~
+; +-current number in actual sample
+; | +time offset of message (ms)
+; | | +ID of message (hex)
+; | | | +data length code
+; | | | | +data bytes (hex) ...
+; | | | | |
+;----+- ---+--- ----+--- + -+ -- -- ...
+ 1) 17535 00000100 8 00 00 00 00 00 00 00 00
+ 2) 17540 FFFFFFFF 4 00 00 00 08 -- -- -- -- BUSHEAVY
+ 3) 17700 00000100 8 00 00 00 00 00 00 00 00
+ 4) 17873 00000100 8 00 00 00 00 00 00 00 00
+ 5) 19295 0000 8 00 00 00 00 00 00 00 00
+ 6) 19500 0000 8 00 00 00 00 00 00 00 00
+ 7) 19705 0000 8 00 00 00 00 00 00 00 00
+ 8) 20592 00000100 8 00 00 00 00 00 00 00 00
+ 9) 20798 00000100 8 00 00 00 00 00 00 00 00
+ 10) 20956 00000100 8 00 00 00 00 00 00 00 00
+ 11) 21097 00000100 8 00 00 00 00 00 00 00 00
diff --git a/test/data/test_CanMessage_V1_1.trc b/test/data/test_CanMessage_V1_1.trc
new file mode 100644
index 000000000..5a02cd59b
--- /dev/null
+++ b/test/data/test_CanMessage_V1_1.trc
@@ -0,0 +1,25 @@
+;$FILEVERSION=1.1
+;$STARTTIME=44548.6028595139
+;
+; Start time: 18.12.2021 14:28:07.062.0
+; Generated by PCAN-View v5.0.0.814
+;
+; Message Number
+; | Time Offset (ms)
+; | | Type
+; | | | ID (hex)
+; | | | | Data Length
+; | | | | | Data Bytes (hex) ...
+; | | | | | |
+;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- --
+ 1) 17535.4 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 2) 17540.3 Warng FFFFFFFF 4 00 00 00 08 BUSHEAVY
+ 3) 17700.3 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 4) 17873.8 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 5) 19295.4 Tx 0000 8 00 00 00 00 00 00 00 00
+ 6) 19500.6 Tx 0000 8 00 00 00 00 00 00 00 00
+ 7) 19705.2 Tx 0000 8 00 00 00 00 00 00 00 00
+ 8) 20592.7 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 9) 20798.6 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 10) 20956.0 Tx 00000100 8 00 00 00 00 00 00 00 00
+ 11) 21097.1 Tx 00000100 8 00 00 00 00 00 00 00 00
diff --git a/test/data/test_CanMessage_V2_0_BUS1.trc b/test/data/test_CanMessage_V2_0_BUS1.trc
new file mode 100644
index 000000000..cf2384df0
--- /dev/null
+++ b/test/data/test_CanMessage_V2_0_BUS1.trc
@@ -0,0 +1,28 @@
+;$FILEVERSION=2.0
+;$STARTTIME=44548.6028595139
+;$COLUMNS=N,O,T,I,d,l,D
+;
+; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_0_BUS1.trc
+; Start time: 18.12.2021 14:28:07.062.001
+; Generated by PEAK-Converter Version 2.2.4.136
+; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc
+;-------------------------------------------------------------------------------
+; Connection Bit rate
+; N/A N/A
+;-------------------------------------------------------------------------------
+; Message Time Type ID Rx/Tx
+; Number Offset | [hex] | Data Length
+; | [ms] | | | | Data [hex] ...
+; | | | | | | |
+;---+-- ------+------ +- --+----- +- +- +- -- -- -- -- -- -- --
+ 1 17535.400 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 2 17540.300 ST Rx 00 00 00 08
+ 3 17700.300 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 4 17873.800 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 5 19295.400 DT 0000 Tx 8 00 00 00 00 00 00 00 00
+ 6 19500.600 DT 0000 Tx 8 00 00 00 00 00 00 00 00
+ 7 19705.200 DT 0000 Tx 8 00 00 00 00 00 00 00 00
+ 8 20592.700 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 9 20798.600 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 10 20956.000 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
+ 11 21097.100 DT 00000100 Tx 8 00 00 00 00 00 00 00 00
diff --git a/test/data/test_CanMessage_V2_1.trc b/test/data/test_CanMessage_V2_1.trc
new file mode 100644
index 000000000..55ceefaf1
--- /dev/null
+++ b/test/data/test_CanMessage_V2_1.trc
@@ -0,0 +1,29 @@
+;$FILEVERSION=2.1
+;$STARTTIME=44548.6028595139
+;$COLUMNS=N,O,T,B,I,d,R,L,D
+;
+; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_1.trc
+; Start time: 18.12.2021 14:28:07.062.001
+; Generated by PEAK-Converter Version 2.2.4.136
+; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc
+;-------------------------------------------------------------------------------
+; Bus Name Connection Protocol
+; N/A N/A N/A N/A
+;-------------------------------------------------------------------------------
+; Message Time Type ID Rx/Tx
+; Number Offset | Bus [hex] | Reserved
+; | [ms] | | | | | Data Length Code
+; | | | | | | | | Data [hex] ...
+; | | | | | | | | |
+;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --
+ 1 17535.400 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 2 17540.300 ST 1 - Rx - 4 00 00 00 08
+ 3 17700.300 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 4 17873.800 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 5 19295.400 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00
+ 6 19500.600 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00
+ 7 19705.200 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00
+ 8 20592.700 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 9 20798.600 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 10 20956.000 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
+ 11 21097.100 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 6a0eafac1..435b651b6 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -13,6 +13,7 @@
"""
import logging
import unittest
+from parameterized import parameterized
import tempfile
import os
from abc import abstractmethod, ABCMeta
@@ -777,8 +778,135 @@ def test_not_crashes_with_file(self):
printer(message)
+class TestTrcFileFormatBase(ReaderWriterTest):
+ """
+ Base class for Tests with can.TRCWriter and can.TRCReader
+
+ .. note::
+ This class is prevented from being executed as a test
+ case itself by a *del* statement in at the end of the file.
+ """
+
+ def _setup_instance(self):
+ super()._setup_instance_helper(
+ can.TRCWriter,
+ can.TRCReader,
+ check_remote_frames=False,
+ check_error_frames=False,
+ check_fd=False,
+ check_comments=False,
+ preserves_channel=False,
+ allowed_timestamp_delta=0.001,
+ adds_default_channel=0,
+ )
+
+ def _read_log_file(self, filename, **kwargs):
+ logfile = os.path.join(os.path.dirname(__file__), "data", filename)
+ with can.TRCReader(logfile, **kwargs) as reader:
+ return list(reader)
+
+
+class TestTrcFileFormatGen(TestTrcFileFormatBase):
+ """Generic tests for can.TRCWriter and can.TRCReader with different file versions"""
+
+ def test_can_message(self):
+ expected_messages = [
+ can.Message(
+ timestamp=2.5010,
+ arbitration_id=0xC8,
+ is_extended_id=False,
+ is_rx=False,
+ channel=1,
+ dlc=8,
+ data=[9, 8, 7, 6, 5, 4, 3, 2],
+ ),
+ can.Message(
+ timestamp=17.876708,
+ arbitration_id=0x6F9,
+ is_extended_id=False,
+ channel=0,
+ dlc=0x8,
+ data=[5, 0xC, 0, 0, 0, 0, 0, 0],
+ ),
+ ]
+ actual = self._read_log_file("test_CanMessage.trc")
+ self.assertMessagesEqual(actual, expected_messages)
+
+ @parameterized.expand(
+ [
+ ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False),
+ ("V1_1", "test_CanMessage_V1_1.trc", True),
+ ("V2_1", "test_CanMessage_V2_1.trc", True),
+ ]
+ )
+ def test_can_message_versions(self, name, filename, is_rx_support):
+ with self.subTest(name):
+
+ def msg_std(timestamp):
+ msg = can.Message(
+ timestamp=timestamp,
+ arbitration_id=0x000,
+ is_extended_id=False,
+ channel=1,
+ dlc=8,
+ data=[0, 0, 0, 0, 0, 0, 0, 0],
+ )
+ if is_rx_support:
+ msg.is_rx = False
+ return msg
+
+ def msg_ext(timestamp):
+ msg = can.Message(
+ timestamp=timestamp,
+ arbitration_id=0x100,
+ is_extended_id=True,
+ channel=1,
+ dlc=8,
+ data=[0, 0, 0, 0, 0, 0, 0, 0],
+ )
+ if is_rx_support:
+ msg.is_rx = False
+ return msg
+
+ expected_messages = [
+ msg_ext(17.5354),
+ msg_ext(17.7003),
+ msg_ext(17.8738),
+ msg_std(19.2954),
+ msg_std(19.5006),
+ msg_std(19.7052),
+ msg_ext(20.5927),
+ msg_ext(20.7986),
+ msg_ext(20.9560),
+ msg_ext(21.0971),
+ ]
+ actual = self._read_log_file(filename)
+ self.assertMessagesEqual(actual, expected_messages)
+
+ def test_not_supported_version(self):
+ with self.assertRaises(NotImplementedError):
+ writer = can.TRCWriter("test.trc")
+ writer.file_version = can.TRCFileVersion.UNKNOWN
+ writer.on_message_received(can.Message())
+
+
+class TestTrcFileFormatV1_0(TestTrcFileFormatBase):
+ """Tests can.TRCWriter and can.TRCReader with file version 1.0"""
+
+ @staticmethod
+ def Writer(filename):
+ writer = can.TRCWriter(filename)
+ writer.file_version = can.TRCFileVersion.V1_0
+ return writer
+
+ def _setup_instance(self):
+ super()._setup_instance()
+ self.writer_constructor = TestTrcFileFormatV1_0.Writer
+
+
# this excludes the base class from being executed as a test case itself
del ReaderWriterTest
+del TestTrcFileFormatBase
if __name__ == "__main__":
From 42549c810f3b85ecbc5c01e0639e8523d30b2370 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Szl=C4=99g?=
Date: Tue, 15 Nov 2022 11:56:52 +0100
Subject: [PATCH 164/475] Add with statement to example in README.md
Not using with expression in code might cause lack of cleanup and hard to trace errors, also because Bus doesn't override __del__.
So it's a good idea to provide a 100% percent correct example in README for people who don't go into examples folder and read into the code.
---
README.rst | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/README.rst b/README.rst
index 11404785a..cd2731696 100644
--- a/README.rst
+++ b/README.rst
@@ -91,21 +91,21 @@ Example usage
# create a bus instance
# many other interfaces are supported as well (see documentation)
- bus = can.Bus(interface='socketcan',
+ with can.Bus(interface='socketcan',
channel='vcan0',
- receive_own_messages=True)
+ receive_own_messages=True) as bus:
- # send a message
- message = can.Message(arbitration_id=123, is_extended_id=True,
- data=[0x11, 0x22, 0x33])
- bus.send(message, timeout=0.2)
+ # send a message
+ message = can.Message(arbitration_id=123, is_extended_id=True,
+ data=[0x11, 0x22, 0x33])
+ bus.send(message, timeout=0.2)
- # iterate over received messages
- for msg in bus:
- print(f"{msg.arbitration_id:X}: {msg.data}")
+ # iterate over received messages
+ for msg in bus:
+ print(f"{msg.arbitration_id:X}: {msg.data}")
- # or use an asynchronous notifier
- notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()])
+ # or use an asynchronous notifier
+ notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()])
You can find more information in the documentation, online at
`python-can.readthedocs.org `__.
From 2e2f157eb02c0505c4e38ca8cbbc54433c65351c Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 21:43:10 +1300
Subject: [PATCH 165/475] Documentation update for 4.1.0 (#1434)
* Quick pass through all interface docs
* Move plugin and virtual interface docs up
* Minor doc spring cleaning
* Update readme to include basic installation
* Remove (mostly unused) mailing list from the readme
* Include TRC section in listener docs
* Fix broken links to virtual interfaces
* Add to changelog
* Refactor api docs
* Fix bug in bus example
* Doc updates after review
---
CHANGELOG.md | 1 +
README.rst | 9 ++--
can/interfaces/cantact.py | 2 +-
can/interfaces/udp_multicast/bus.py | 2 +-
can/interfaces/virtual.py | 2 +-
doc/api.rst | 28 +---------
doc/bus.rst | 65 +++++++++++++++--------
doc/errors.rst | 8 +++
doc/history.rst | 4 +-
doc/index.rst | 16 +++---
doc/installation.rst | 21 +++++---
doc/interfaces.rst | 67 +++---------------------
doc/interfaces/canalystii.rst | 2 +-
doc/interfaces/etas.rst | 29 +++++++----
doc/interfaces/gs_usb.rst | 37 +++++++++----
doc/interfaces/ixxat.rst | 61 +++++++++++-----------
doc/interfaces/kvaser.rst | 4 +-
doc/interfaces/neovi.rst | 12 ++---
doc/interfaces/nican.rst | 6 +--
doc/interfaces/nixnet.rst | 8 +--
doc/interfaces/pcan.rst | 24 +++++----
doc/interfaces/robotell.rst | 14 +----
doc/interfaces/seeedstudio.rst | 17 ++----
doc/interfaces/socketcan.rst | 26 ++++-----
doc/interfaces/socketcand.rst | 4 +-
doc/interfaces/systec.rst | 14 ++---
doc/interfaces/udp_multicast.rst | 2 +-
doc/interfaces/usb2can.rst | 81 ++++++++++++++++-------------
doc/interfaces/vector.rst | 3 ++
doc/interfaces/virtual.rst | 72 +------------------------
doc/internal-api.rst | 9 ++++
doc/listeners.rst | 40 +++++++++++++-
doc/plugin-interface.rst | 54 +++++++++++++++++++
doc/scripts.rst | 2 +-
doc/utils.rst | 7 +++
doc/virtual-interfaces.rst | 77 +++++++++++++++++++++++++++
examples/print_notifier.py | 19 +++++++
examples/vcan_filtered.py | 2 +-
38 files changed, 486 insertions(+), 365 deletions(-)
create mode 100644 doc/errors.rst
create mode 100644 doc/plugin-interface.rst
create mode 100644 doc/utils.rst
create mode 100644 doc/virtual-interfaces.rst
create mode 100755 examples/print_notifier.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f10e2b78..04ffb9b57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ Features
xlFlushReceiveQueue to xldriver (#1387).
* Vector: Raise a CanInitializationError, if the CAN settings can not
be applied according to the arguments of ``VectorBus.__init__`` (#1426).
+* Ixxat bus now implements BusState api and detects errors (#1141)
Bug Fixes
---------
diff --git a/README.rst b/README.rst
index 11404785a..79b985129 100644
--- a/README.rst
+++ b/README.rst
@@ -62,7 +62,7 @@ Library Version Python
------------------------------ -----------
2.x 2.6+, 3.4+
3.x 2.7+, 3.5+
- 4.x *(currently on develop)* 3.7+
+ 4.x 3.7+
============================== ===========
@@ -74,7 +74,7 @@ Features
- receiving, sending, and periodically sending messages
- normal and extended arbitration IDs
- `CAN FD `__ support
-- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite and Canutils log
+- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), TRC, CSV, SQLite, and Canutils log
- efficient in-kernel or in-hardware filtering of messages on supported interfaces
- bus configuration reading from a file or from environment variables
- command line tools for working with CAN buses (see the `docs `__)
@@ -84,6 +84,8 @@ Features
Example usage
-------------
+``pip install python-can``
+
.. code:: python
# import the library
@@ -117,9 +119,6 @@ Discussion
If you run into bugs, you can file them in our
`issue tracker `__ on GitHub.
-There is also a `python-can `__
-mailing list for development discussion.
-
`Stackoverflow `__ has several
questions and answers tagged with ``python+can``.
diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py
index 9ad7fbef8..d735b7ee3 100644
--- a/can/interfaces/cantact.py
+++ b/can/interfaces/cantact.py
@@ -20,7 +20,7 @@
except ImportError:
cantact = None
logger.warning(
- "The CANtact module is not installed. Install it using `python -m pip install cantact`"
+ "The CANtact module is not installed. Install it using `pip install cantact`"
)
diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py
index 7f74c685f..2ba1205b1 100644
--- a/can/interfaces/udp_multicast/bus.py
+++ b/can/interfaces/udp_multicast/bus.py
@@ -55,7 +55,7 @@ class UdpMulticastBus(BusABC):
.. warning::
This interface does not make guarantees on reliable delivery and message ordering, and also does not
implement rate limiting or ID arbitration/prioritization under high loads. Please refer to the section
- :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives.
+ :ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives.
:param channel: A multicast IPv4 address (in `224.0.0.0/4`) or an IPv6 address (in `ff00::/8`).
This defines which version of IP is used. See
diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py
index cc71469b5..25b7abfb0 100644
--- a/can/interfaces/virtual.py
+++ b/can/interfaces/virtual.py
@@ -51,7 +51,7 @@ class VirtualBus(BusABC):
.. warning::
This interface guarantees reliable delivery and message ordering, but does *not* implement rate
limiting or ID arbitration/prioritization under high loads. Please refer to the section
- :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives.
+ :ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives.
"""
def __init__(
diff --git a/doc/api.rst b/doc/api.rst
index 23342f992..053bd34a4 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -17,33 +17,9 @@ A form of CAN interface is also required.
listeners
asyncio
bcm
+ errors
bit_timing
+ utils
internal-api
-Utilities
----------
-
-
-.. autofunction:: can.detect_available_configs
-
-
-.. _notifier:
-
-Notifier
---------
-
-The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners.
-
-.. autoclass:: can.Notifier
- :members:
-
-
-.. _errors:
-
-Errors
-------
-
-.. automodule:: can.exceptions
- :members:
- :show-inheritance:
diff --git a/doc/bus.rst b/doc/bus.rst
index 4db49ee29..06a740829 100644
--- a/doc/bus.rst
+++ b/doc/bus.rst
@@ -3,48 +3,55 @@
Bus
---
-The :class:`~can.BusABC` class, as the name suggests, provides an abstraction of a CAN bus.
-The bus provides a wrapper around a physical or virtual CAN Bus.
-An interface specific instance of the :class:`~can.BusABC` is created by the :class:`~can.Bus`
-class, for example::
+The :class:`~can.Bus` provides a wrapper around a physical or virtual CAN Bus.
+
+An interface specific instance is created by instantiating the :class:`~can.Bus`
+class with a particular ``interface``, for example::
vector_bus = can.Bus(interface='vector', ...)
-That bus is then able to handle the interface specific software/hardware interactions
-and implements the :class:`~can.BusABC` API.
+The created bus is then able to handle the interface specific software/hardware interactions
+while giving the user the same top level API.
A thread safe bus wrapper is also available, see `Thread safe bus`_.
-.. autoclass:: can.Bus
- :class-doc-from: class
- :show-inheritance:
- :members:
- :inherited-members:
-
-.. autoclass:: can.bus.BusState
- :members:
- :undoc-members:
-
Transmitting
''''''''''''
Writing individual messages to the bus is done by calling the :meth:`~can.BusABC.send` method
-and passing a :class:`~can.Message` instance. Periodic sending is controlled by the
-:ref:`broadcast manager `.
+and passing a :class:`~can.Message` instance.
+
+.. code-block:: python
+ :emphasize-lines: 8
+
+ with can.Bus() as bus:
+ msg = can.Message(
+ arbitration_id=0xC0FFEE,
+ data=[0, 25, 0, 1, 3, 1, 4, 1],
+ is_extended_id=True
+ )
+ try:
+ bus.send(msg)
+ print(f"Message sent on {bus.channel_info}")
+ except can.CanError:
+ print("Message NOT sent")
+Periodic sending is controlled by the :ref:`broadcast manager `.
+
Receiving
'''''''''
Reading from the bus is achieved by either calling the :meth:`~can.BusABC.recv` method or
by directly iterating over the bus::
- for msg in bus:
- print(msg.data)
+ with can.Bus() as 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.
+Alternatively the :ref:`listeners_doc` api can be used, which is a list of various
+:class:`~can.Listener` implementations that receive and handle messages from a :class:`~can.Notifier`.
Filtering
@@ -67,6 +74,20 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass
See :meth:`~can.BusABC.set_filters` for the implementation.
+Bus API
+'''''''
+
+.. autoclass:: can.Bus
+ :class-doc-from: class
+ :show-inheritance:
+ :members:
+ :inherited-members:
+
+.. autoclass:: can.bus.BusState
+ :members:
+ :undoc-members:
+
+
Thread safe bus
'''''''''''''''
diff --git a/doc/errors.rst b/doc/errors.rst
new file mode 100644
index 000000000..bc954738a
--- /dev/null
+++ b/doc/errors.rst
@@ -0,0 +1,8 @@
+.. _errors:
+
+Error Handling
+==============
+
+.. automodule:: can.exceptions
+ :members:
+ :show-inheritance:
diff --git a/doc/history.rst b/doc/history.rst
index 9ae0581b0..73371af4c 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -1,5 +1,5 @@
-History and Roadmap
-===================
+History
+=======
Background
----------
diff --git a/doc/index.rst b/doc/index.rst
index f24831c7c..505c8b87b 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -8,22 +8,22 @@ different hardware devices, and a suite of utilities for sending and receiving
messages on a CAN bus.
**python-can** runs any where Python runs; from high powered computers
-with commercial `CAN to usb` devices right down to low powered devices running
+with commercial `CAN to USB` devices right down to low powered devices running
linux such as a BeagleBone or RaspberryPi.
More concretely, some example uses of the library:
-- Passively logging what occurs on a CAN bus. For example monitoring a
+* Passively logging what occurs on a CAN bus. For example monitoring a
commercial vehicle using its **OBD-II** port.
-- Testing of hardware that interacts via CAN. Modules found in
- modern cars, motocycles, boats, and even wheelchairs have had components tested
+* Testing of hardware that interacts via CAN. Modules found in
+ modern cars, motorcycles, boats, and even wheelchairs have had components tested
from Python using this library.
-- Prototyping new hardware modules or software algorithms in-the-loop. Easily
+* Prototyping new hardware modules or software algorithms in-the-loop. Easily
interact with an existing bus.
-- Creating virtual modules to prototype CAN bus communication.
+* Creating virtual modules to prototype CAN bus communication.
Brief example of the library in action: connecting to a CAN bus, creating and sending a message:
@@ -37,12 +37,14 @@ Brief example of the library in action: connecting to a CAN bus, creating and se
Contents:
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
installation
configuration
api
interfaces
+ virtual-interfaces
+ plugin-interface
scripts
development
history
diff --git a/doc/installation.rst b/doc/installation.rst
index bfce72180..6b2a2cfb2 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -2,15 +2,24 @@ Installation
============
-Install ``can`` with ``pip``:
-::
+Install the ``can`` package from PyPi with ``pip`` or similar::
$ pip install python-can
-As most likely you will want to interface with some hardware, you may
-also have to install platform dependencies. Be sure to check any other
-specifics for your hardware in :doc:`interfaces`.
+
+
+.. warning::
+ As most likely you will want to interface with some hardware, you may
+ also have to install platform dependencies. Be sure to check any other
+ specifics for your hardware in :doc:`interfaces`.
+
+ Many interfaces can install their dependencies at the same time as ``python-can``,
+ for instance the interface ``serial`` includes the ``pyserial`` dependency which can
+ be installed with the ``serial`` extra::
+
+ $ pip install python-can[serial]
+
GNU/Linux dependencies
@@ -99,7 +108,7 @@ To install ``python-can`` using the CANtact driver backend:
If ``python-can`` is already installed, the CANtact backend can be installed separately:
-``python3 -m pip install cantact``
+``pip install cantact``
Additional CANtact documentation is available at `cantact.io `__.
diff --git a/doc/interfaces.rst b/doc/interfaces.rst
index 54d70ca86..cc686d2d5 100644
--- a/doc/interfaces.rst
+++ b/doc/interfaces.rst
@@ -1,14 +1,18 @@
.. _can interface modules:
-CAN Interface Modules
----------------------
+Hardware Interfaces
+===================
**python-can** hides the low-level, device-specific interfaces to controller
area network adapters in interface dependant modules. However as each hardware
device is different, you should carefully go through your interface's
documentation.
-The available interfaces are:
+.. note::
+ The *Interface Names* are listed in :doc:`configuration`.
+
+
+The available hardware interfaces are:
.. toctree::
:maxdepth: 1
@@ -32,63 +36,6 @@ The available interfaces are:
interfaces/socketcan
interfaces/socketcand
interfaces/systec
- interfaces/udp_multicast
interfaces/usb2can
interfaces/vector
- interfaces/virtual
-
-The *Interface Names* are listed in :doc:`configuration`.
-
-
-.. _plugin interface:
-
-Plugin Interface
-^^^^^^^^^^^^^^^^
-
-External packages can register new interfaces by using the ``can.interface`` entry point
-in its project configuration. The format of the entry point depends on your project
-configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*).
-
-In the following example ``module`` defines the location of your bus class inside your
-package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of
-your :class:`can.BusABC` subclass.
-
-.. tab:: pyproject.toml (PEP 621)
-
- .. code-block:: toml
-
- # Note the quotes around can.interface in order to escape the dot .
- [project.entry-points."can.interface"]
- interface_name = "module:classname"
-
-.. tab:: setup.cfg
-
- .. code-block:: ini
-
- [options.entry_points]
- can.interface =
- interface_name = module:classname
-
-.. tab:: setup.py
-
- .. code-block:: python
-
- from setuptools import setup
-
- setup(
- # ...,
- entry_points = {
- 'can.interface': [
- 'interface_name = module:classname'
- ]
- }
- )
-
-The ``interface_name`` can be used to
-create an instance of the bus in the **python-can** API:
-
-.. code-block:: python
-
- import can
- bus = can.Bus(interface="interface_name", channel=0)
diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst
index 375e1b754..b48782259 100644
--- a/doc/interfaces/canalystii.rst
+++ b/doc/interfaces/canalystii.rst
@@ -12,7 +12,7 @@ Windows, Linux and Mac.
.. note::
- The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the Canalyst-II USB device driver to ``libusb-win32``.
+ The backend driver depends on `pyusb `_ so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig `_ can be used to set the Canalyst-II USB device driver to ``libusb-win32``.
Limitations
-----------
diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst
index 2b59a4eee..7986142be 100644
--- a/doc/interfaces/etas.rst
+++ b/doc/interfaces/etas.rst
@@ -3,19 +3,22 @@ ETAS
This interface adds support for CAN interfaces by `ETAS`_.
The ETAS BOA_ (Basic Open API) is used.
+
+Installation
+------------
+
Install the "ETAS ECU and Bus Interfaces – Distribution Package".
-Only Windows is supported by this interface.
-The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules.
-To use these under Linux, please refer to :ref:`SocketCAN`.
-Bus
----
+.. warning::
+ Only Windows is supported by this interface.
-.. autoclass:: can.interfaces.etas.EtasBus
- :members:
+ The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1
+ USB modules. To use these under Linux, please refer to the :ref:`SocketCAN` interface
+ documentation.
-Configuration file
-------------------
+
+Configuration
+-------------
The simplest configuration file would be::
@@ -31,5 +34,13 @@ To find available URIs, use :meth:`~can.detect_available_configs`::
for c in configs:
print(c)
+
+Bus
+---
+
+.. autoclass:: can.interfaces.etas.EtasBus
+ :members:
+
+
.. _ETAS: https://www.etas.com/
.. _BOA: https://www.etas.com/de/downloadcenter/18102.php
diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst
index 232786fb7..af69581be 100755
--- a/doc/interfaces/gs_usb.rst
+++ b/doc/interfaces/gs_usb.rst
@@ -1,9 +1,10 @@
.. _gs_usb:
-CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces
-==================================================================================================================
+Geschwister Schneider and candleLight
+=====================================
-Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces.
+Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices
+and candleLight USB CAN interfaces.
Install: ``pip install "python-can[gs_usb]"``
@@ -17,13 +18,19 @@ Usage: pass device ``index`` (starting from 0) if using automatic device detecti
Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below:
-::
+.. code-block:: python
import usb
import can
dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F)
- bus = can.Bus(bustype="gs_usb", channel=dev.product, bus=dev.bus, address=dev.address, bitrate=250000)
+ bus = can.Bus(
+ bustype="gs_usb",
+ channel=dev.product,
+ bus=dev.bus,
+ address=dev.address,
+ bitrate=250000
+ )
Supported devices
@@ -39,21 +46,29 @@ Windows, Linux and Mac.
.. note::
- The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the USB device driver to ``libusb-win32``.
+ The backend driver depends on `pyusb `_ so a ``pyusb`` backend driver library such as
+ ``libusb`` must be installed.
+
+ On Windows a tool such as `Zadig `_ can be used to set the USB device driver to
+ ``libusb-win32``.
-Supplementary Info on ``gs_usb``
------------------------------------
+Supplementary Info
+------------------
The firmware implementation for Geschwister Schneider USB/CAN devices and candleLight USB CAN can be found in `candle-usb/candleLight_fw `_.
The Linux kernel driver can be found in `linux/drivers/net/can/usb/gs_usb.c `_.
-The ``gs_usb`` interface in ``PythonCan`` relys on upstream ``gs_usb`` package, which can be found in `https://pypi.org/project/gs-usb/ `_ or `https://github.com/jxltom/gs_usb `_.
-The ``gs_usb`` package is using ``pyusb`` as backend, which brings better crossplatform compatibility.
+The ``gs_usb`` interface in ``python-can`` relies on upstream ``gs_usb`` package, which can be found in
+`https://pypi.org/project/gs-usb/ `_ or
+`https://github.com/jxltom/gs_usb `_.
+
+The ``gs_usb`` package uses ``pyusb`` as backend, which brings better cross-platform compatibility.
Note: The bitrate ``10K``, ``20K``, ``50K``, ``83.333K``, ``100K``, ``125K``, ``250K``, ``500K``, ``800K`` and ``1M`` are supported in this interface, as implemented in the upstream ``gs_usb`` package's ``set_bitrate`` method.
-Note: Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces.
+.. warning::
+ Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces.
Bus
---
diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst
index 02e707c1c..61df70638 100644
--- a/doc/interfaces/ixxat.rst
+++ b/doc/interfaces/ixxat.rst
@@ -1,9 +1,9 @@
.. _ixxatdoc:
-IXXAT Virtual CAN Interface
-===========================
+IXXAT Virtual Communication Interface
+=====================================
-Interface to `IXXAT `__ Virtual CAN Interface V3 SDK. Works on Windows.
+Interface to `IXXAT `__ Virtual Communication Interface V3 SDK. Works on Windows.
The Linux ECI SDK is currently unsupported, however on Linux some devices are
supported with :doc:`socketcan`.
@@ -14,33 +14,8 @@ Modifying cyclic messages is not possible. You will need to stop it, and then
start a new periodic message.
-Bus
----
-
-.. autoclass:: can.interfaces.ixxat.IXXATBus
- :members:
-
-Implementation based on vcinpl.dll
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus
- :members:
-
-.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask
- :members:
-
-Implementation based on vcinpl2.dll
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus
- :members:
-
-.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask
- :members:
-
-
-Configuration file
-------------------
+Configuration
+-------------
The simplest configuration file would be::
[default]
@@ -91,6 +66,32 @@ To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids(
Found IXXAT with hardware id 'HW107422'.
+Bus
+---
+
+.. autoclass:: can.interfaces.ixxat.IXXATBus
+ :members:
+
+Implementation based on vcinpl.dll
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus
+ :members:
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask
+ :members:
+
+Implementation based on vcinpl2.dll
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus
+ :members:
+
+.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask
+ :members:
+
+
+
Internals
---------
diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst
index 4e0062cfa..f2c93f85b 100644
--- a/doc/interfaces/kvaser.rst
+++ b/doc/interfaces/kvaser.rst
@@ -20,7 +20,7 @@ Internals
The Kvaser :class:`~can.Bus` object with a physical CAN Bus can be operated in two
modes; ``single_handle`` mode with one shared bus handle used for both reading and
writing to the CAN bus, or with two separate bus handles.
-Two separate handles are needed if receiving and sending messages are done in
+Two separate handles are needed if receiving and sending messages in
different threads (see `Kvaser documentation
`_).
@@ -40,7 +40,7 @@ in the ``recv`` method. If a message does not match any of the filters,
Custom methods
-~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~
This section contains Kvaser driver specific methods.
diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst
index 588c5e914..0baf08055 100644
--- a/doc/interfaces/neovi.rst
+++ b/doc/interfaces/neovi.rst
@@ -1,7 +1,7 @@
-neoVI
-=====
+Intrepid Control Systems neoVI
+==============================
-.. warning::
+.. note::
This ``ICS neoVI`` documentation is a work in progress. Feedback and revisions
are most welcome!
@@ -14,16 +14,16 @@ wrapper on Windows.
Installation
------------
-This neoVI interface requires the installation of the ICS neoVI DLL and python-ics
+This neoVI interface requires the installation of the ICS neoVI DLL and ``python-ics``
package.
- Download and install the Intrepid Product Drivers
`Intrepid Product Drivers `__
-- Install python-ics
+- Install ``python-can`` with the ``neovi`` extras:
.. code-block:: bash
- pip install python-ics
+ pip install python-ics[neovi]
Configuration
diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst
index 4d2a40717..6e802a3d4 100644
--- a/doc/interfaces/nican.rst
+++ b/doc/interfaces/nican.rst
@@ -1,7 +1,7 @@
-NI-CAN
-======
+National Instruments NI-CAN
+===========================
-This interface adds support for CAN controllers by `National Instruments`_.
+This interface adds support for NI-CAN controllers by `National Instruments`_.
.. warning::
diff --git a/doc/interfaces/nixnet.rst b/doc/interfaces/nixnet.rst
index 36d1a8ef0..8cf2ee72d 100644
--- a/doc/interfaces/nixnet.rst
+++ b/doc/interfaces/nixnet.rst
@@ -1,12 +1,12 @@
-NI-XNET
-=======
+National Instruments NI-XNET
+============================
This interface adds support for NI-XNET CAN controllers by `National Instruments`_.
-.. warning::
+.. note::
- NI-XNET only seems to support windows platforms.
+ NI-XNET only supports windows platforms.
Bus
diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst
index feb40b195..790264627 100644
--- a/doc/interfaces/pcan.rst
+++ b/doc/interfaces/pcan.rst
@@ -3,7 +3,7 @@
PCAN Basic API
==============
-Interface to `Peak-System `__'s PCAN-Basic API.
+Interface to `Peak-System `__'s PCAN-Basic API.
Configuration
-------------
@@ -18,15 +18,9 @@ Here is an example configuration file for using `PCAN-USB The socketcan package is an implementation of CAN protocols
-> (Controller Area Network) for Linux. CAN is a networking technology
-> which has widespread use in automation, embedded devices, and
-> automotive fields. While there have been other CAN implementations
-> for Linux based on character devices, SocketCAN uses the Berkeley
-> socket API, the Linux network stack and implements the CAN device
-> drivers as network interfaces. The CAN socket API has been designed
-> as similar as possible to the TCP/IP protocols to allow programmers,
-> familiar with network programming, to easily learn how to use CAN
-> sockets.
+The SocketCAN documentation can be found in the `Linux kernel docs`_ in the
+``networking`` directory. Quoting from the SocketCAN Linux documentation:
+
+ The socketcan package is an implementation of CAN protocols
+ (Controller Area Network) for Linux. CAN is a networking technology
+ which has widespread use in automation, embedded devices, and
+ automotive fields. While there have been other CAN implementations
+ for Linux based on character devices, SocketCAN uses the Berkeley
+ socket API, the Linux network stack and implements the CAN device
+ drivers as network interfaces. The CAN socket API has been designed
+ as similar as possible to the TCP/IP protocols to allow programmers,
+ familiar with network programming, to easily learn how to use CAN
+ sockets.
.. important::
diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst
index 3c05bcc85..2f313470c 100644
--- a/doc/interfaces/socketcand.rst
+++ b/doc/interfaces/socketcand.rst
@@ -4,7 +4,7 @@ socketcand Interface
====================
`Socketcand `__ is part of the
`Linux-CAN `__ project, providing a
-Network-to-CAN bridge as Linux damon. It implements a specific
+Network-to-CAN bridge as a Linux damon. It implements a specific
`TCP/IP based communication protocol `__
to transfer CAN frames and control commands.
@@ -24,7 +24,7 @@ daemon running on a remote Raspberry Pi:
try:
while True:
msg = bus.recv()
- print (msg)
+ print(msg)
except KeyboardInterrupt:
pass
diff --git a/doc/interfaces/systec.rst b/doc/interfaces/systec.rst
index 0aa4d9444..6b04fdfe0 100644
--- a/doc/interfaces/systec.rst
+++ b/doc/interfaces/systec.rst
@@ -28,12 +28,6 @@ The interface supports following devices:
- USB-CANmodul1 G4,
- USB-CANmodul2 G4.
-Bus
----
-
-.. autoclass:: can.interfaces.systec.ucanbus.UcanBus
- :members:
-
Configuration
-------------
@@ -57,6 +51,14 @@ Optional parameters:
* ``state`` (default BusState.ACTIVE) BusState of the channel
* ``receive_own_messages`` (default False) If messages transmitted should also be received back
+
+Bus
+---
+
+.. autoclass:: can.interfaces.systec.ucanbus.UcanBus
+ :members:
+
+
Internals
---------
diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst
index f2775727c..b15354ed5 100644
--- a/doc/interfaces/udp_multicast.rst
+++ b/doc/interfaces/udp_multicast.rst
@@ -16,7 +16,7 @@ sufficiently reliable for this interface to function properly.
.. note::
For an overview over the different virtual buses in this library and beyond, please refer
- to the section :ref:`other_virtual_interfaces`. It also describes important limitations
+ to the section :ref:`virtual_interfaces_doc`. It also describes important limitations
of this interface.
Please refer to the `Bus class documentation`_ below for configuration options and useful resources
diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst
index 56243d41d..1ac9dd61f 100644
--- a/doc/interfaces/usb2can.rst
+++ b/doc/interfaces/usb2can.rst
@@ -1,36 +1,35 @@
USB2CAN Interface
=================
-OVERVIEW
---------
-
The `USB2CAN `_ is a cheap CAN interface based on an ARM7 chip (STR750FV2).
There is support for this device on Linux through the :doc:`socketcan` interface and for Windows using this
``usb2can`` interface.
-
-WINDOWS SUPPORT
----------------
-
Support though windows is achieved through a DLL very similar to the way the PCAN functions. The API is called CANAL
(CAN Abstraction Layer) which is a separate project designed to be used with VSCP which is a socket like messaging system
-that is not only cross platform but also supports other types of devices. This device can be used through one of three ways
-1)Through python-can
-2)CANAL API either using the DLL and C/C++ or through the python wrapper that has been added to this project
-3)VSCP
-Using python-can is strongly suggested as with little extra work the same interface can be used on both Windows and Linux.
+that is not only cross platform but also supports other types of devices.
+
+
+Installation
+------------
+
+1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1)
+
+2. Install the appropriate version of `pywin32 `_ (win32com)
+
+3. Download the USB2CAN CANAL DLL from the USB2CAN website.
+ Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install.
+ Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment.
+
+
+Internals
+---------
-WINDOWS INSTALL
----------------
+This interface originally written against CANAL DLL version ``v1.0.6``.
- 1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1)
- 2. Install the appropriate version of `pywin32 `_ (win32com)
- 3. Download the USB2CAN CANAL DLL from the USB2CAN website. Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install.
- Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment.
- (Written against CANAL DLL version v1.0.6)
Interface Layout
-----------------
+~~~~~~~~~~~~~~~~
- ``usb2canabstractionlayer.py``
This file is only a wrapper for the CANAL API that the interface expects. There are also a couple of constants here to try and make dealing with the
@@ -50,20 +49,26 @@ Interface Layout
Interface Specific Items
------------------------
-There are a few things that are kinda strange about this device and are not overly obvious about the code or things that are not done being implemented in the DLL.
-
-1. You need the Serial Number to connect to the device under Windows. This is part of the "setup string" that configures the device. There are a few options for how to get this.
- 1. Use usb2canWin.py to find the serial number
- 2. Look on the device and enter it either through a prompt/barcode scanner/hardcode it.(Not recommended)
- 3. Reprogram the device serial number to something and do that for all the devices you own. (Really Not Recommended, can no longer use multiple devices on one computer)
+There are a few things that are kinda strange about this device and are not overly obvious about the code or things that
+are not done being implemented in the DLL.
+
+1. You need the Serial Number to connect to the device under Windows. This is part of the "setup string" that configures the device. There are a few options for how to get this.
+
+ 1. Use ``usb2canWin.py`` to find the serial number.
+ 2. Look on the device and enter it either through a prompt/barcode scanner/hardcode it. (Not recommended)
+ 3. Reprogram the device serial number to something and do that for all the devices you own. (Really Not Recommended, can no longer use multiple devices on one computer)
-2. In usb2canabstractionlayer.py there is a structure called CanalMsg which has a unsigned byte array of size 8. In the usb2canInterface file it passes in an unsigned byte array of
- size 8 also which if you pass less than 8 bytes in it stuffs it with extra zeros. So if the data "01020304" is sent the message would look like "0102030400000000".
- There is also a part of this structure called sizeData which is the actual length of the data that was sent not the stuffed message (in this case would be 4).
- What then happens is although a message of size 8 is sent to the device only the length of information so the first 4 bytes of information would be sent. This
- is done because the DLL expects a length of 8 and nothing else. So to make it compatible that has to be sent through the wrapper. If usb2canInterface sent an
- array of length 4 with sizeData of 4 as well the array would throw an incompatible data type error. There is a Wireshark file posted in Issue #36 that demonstrates
- that the bus is only sending the data and not the extra zeros.
+2. In ``usb2canabstractionlayer.py`` there is a structure called ``CanalMsg`` which has a unsigned byte array of size 8.
+ In the ``usb2canInterface`` file it passes in an unsigned byte array of size 8 also which if you pass less than 8
+ bytes in it stuffs it with extra zeros. So if the data ``"01020304"`` is sent the message would look like
+ ``"0102030400000000"``.
+
+ There is also a part of this structure called ``sizeData`` which is the actual length of the data that was sent not
+ the stuffed message (in this case would be 4). What then happens is although a message of size 8 is sent to the device
+ only the first 4 bytes of information would be sent. This is done because the DLL expects a length of 8 and nothing
+ else. So to make it compatible that has to be sent through the wrapper. If ``usb2canInterface`` sent an
+ array of length 4 with sizeData of 4 as well the array would throw an incompatible data type error.
+
3. The masking features have not been implemented currently in the CANAL interface in the version currently on the USB2CAN website.
@@ -79,12 +84,16 @@ Bus
.. autoclass:: can.interfaces.usb2can.Usb2canBus
+Exceptions
+----------
+
+.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError
-Internals
----------
+
+Miscellaneous
+-------------
.. autoclass:: can.interfaces.usb2can.Usb2CanAbstractionLayer
:members:
:undoc-members:
-.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError
diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst
index 7f9aa1f3f..56961e7e2 100644
--- a/doc/interfaces/vector.rst
+++ b/doc/interfaces/vector.rst
@@ -3,6 +3,9 @@ Vector
This interface adds support for CAN controllers by `Vector`_. Only Windows is supported.
+Configuration
+-------------
+
By default this library uses the channel configuration for CANalyzer.
To use a different application, open **Vector Hardware Configuration** program and create
a new application and assign the channels you may want to use.
diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst
index 29976ed47..7569ffeb9 100644
--- a/doc/interfaces/virtual.rst
+++ b/doc/interfaces/virtual.rst
@@ -8,79 +8,9 @@ Any `VirtualBus` instances connecting to the same channel (from within the same
process) will receive each others messages.
If messages shall be sent across process or host borders, consider using the
-:ref:`udp_multicast_doc` and refer to (:ref:`the next section `)
+:ref:`udp_multicast_doc` and refer to :ref:`virtual_interfaces_doc`
for a comparison and general discussion of different virtual interfaces.
-.. _other_virtual_interfaces:
-
-Other Virtual Interfaces
-------------------------
-
-There are quite a few implementations for CAN networks that do not require physical
-CAN hardware.
-This section also describes common limitations of current virtual interfaces.
-
-Comparison
-''''''''''
-
-The following table compares some known virtual interfaces:
-
-+----------------------------------------------------+-----------------------------------------------------------------------+---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+
-| **Name** | **Availability** | **Applicability** | **Implementation** |
-| | +-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-| | | **Within | **Between | **Via (IP) | **Without Central | **Transport | **Serialization |
-| | | Process** | Processes** | Networks** | Server** | Technology** | Format** |
-+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-| ``virtual`` (this) | *included* | ✓ | ✗ | ✗ | ✓ | Singleton & Mutex | none |
-| | | | | | | (reliable) | |
-+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-| ``udp_multicast`` (:ref:`doc `) | *included* | ✓ | ✓ | ✓ | ✓ | UDP via IP multicast | custom using `msgpack `__ |
-| | | | | | | (unreliable) | |
-+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-| *christiansandberg/ | `external `__ | ✓ | ✓ | ✓ | ✗ | Websockets via TCP/IP | custom binary |
-| python-can-remote* | | | | | | (reliable) | |
-+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-| *windelbouwman/ | `external `__ | ✓ | ✓ | ✓ | ✗ | `ZeroMQ `__ via TCP/IP | custom binary [#f1]_ |
-| virtualcan* | | | | | | (reliable) | |
-+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
-
-.. [#f1]
- The only option in this list that implements interoperability with other languages
- out of the box. For the others (except the first intra-process one), other programs written
- in potentially different languages could effortlessly interface with the bus
- once they mimic the serialization format. The last one, however, has already implemented
- the entire bus functionality in *C++* and *Rust*, besides the Python variant.
-
-Common Limitations
-''''''''''''''''''
-
-**Guaranteed delivery** and **message ordering** is one major point of difference:
-While in a physical CAN network, a message is either sent or in queue (or an explicit error occurred),
-this may not be the case for virtual networks.
-The ``udp_multicast`` bus for example, drops this property for the benefit of lower
-latencies by using unreliable UDP/IP instead of reliable TCP/IP (and because normal IP multicast
-is inherently unreliable, as the recipients are unknown by design). The other three buses faithfully
-model a physical CAN network in this regard: They ensure that all recipients actually receive
-(and acknowledge each message), much like in a physical CAN network. They also ensure that
-messages are relayed in the order they have arrived at the central server and that messages
-arrive at the recipients exactly once. Both is not guaranteed to hold for the best-effort
-``udp_multicast`` bus as it uses UDP/IP as a transport layer.
-
-**Central servers** are, however, required by interfaces 3 and 4 (the external tools) to provide
-these guarantees of message delivery and message ordering. The central servers receive and distribute
-the CAN messages to all other bus participants, unlike in a real physical CAN network.
-The first intra-process ``virtual`` interface only runs within one Python process, effectively the
-Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server.
-Notably the ``udp_multicast`` bus does not require a central server.
-
-**Arbitration and throughput** are two interrelated functions/properties of CAN networks which
-are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount
-of messages can be sent per unit of time (given the computational power of the machines and
-networks that are involved). In a real CAN/CAN FD networks, however, throughput is usually much
-more restricted and prioritization of arbitration IDs is thus an important feature once the bus
-is starting to get saturated. None of the interfaces presented above support any sort of throttling
-or ID arbitration under high loads.
-
Example
-------
diff --git a/doc/internal-api.rst b/doc/internal-api.rst
index 3ef599598..b8c108fb5 100644
--- a/doc/internal-api.rst
+++ b/doc/internal-api.rst
@@ -7,6 +7,15 @@ Here we document the odds and ends that are more helpful for creating your own i
or listeners but generally shouldn't be required to interact with python-can.
+BusABC
+------
+
+The :class:`~can.BusABC` class, as the name suggests, provides an abstraction of a CAN bus.
+The bus provides a wrapper around a physical or virtual CAN Bus.
+
+An interface specific instance of the :class:`~can.BusABC` is created by the :class:`~can.Bus`
+class, see :ref:`bus` for the user facing API.
+
.. _businternals:
diff --git a/doc/listeners.rst b/doc/listeners.rst
index ad18aff24..260854d2a 100644
--- a/doc/listeners.rst
+++ b/doc/listeners.rst
@@ -1,5 +1,18 @@
-Listeners
-=========
+
+Reading and Writing Messages
+============================
+
+.. _notifier:
+
+Notifier
+--------
+
+The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners.
+
+.. autoclass:: can.Notifier
+ :members:
+
+.. _listeners_doc:
Listener
--------
@@ -12,6 +25,12 @@ message, or by calling the method **on_message_received**.
Listeners are registered with :ref:`notifier` object(s) which ensure they are
notified whenever a new message is received.
+.. literalinclude:: ../examples/print_notifier.py
+ :language: python
+ :linenos:
+ :emphasize-lines: 8,9
+
+
Subclasses of Listener that do not override **on_message_received** will cause
:class:`NotImplementedError` to be thrown when a message is received on
the CAN bus.
@@ -191,3 +210,20 @@ The following class can be used to read messages from BLF file:
.. autoclass:: can.BLFReader
:members:
+
+TRC
+----
+
+Implements basic support for the TRC file format.
+
+
+.. note::
+ Comments and contributions are welcome on what file versions might be relevant.
+
+.. autoclass:: can.TRCWriter
+ :members:
+
+The following class can be used to read messages from TRC file:
+
+.. autoclass:: can.TRCReader
+ :members:
diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst
new file mode 100644
index 000000000..14c3f51d5
--- /dev/null
+++ b/doc/plugin-interface.rst
@@ -0,0 +1,54 @@
+
+.. _plugin interface:
+
+Plugin Interface
+================
+
+External packages can register new interfaces by using the ``can.interface`` entry point
+in its project configuration. The format of the entry point depends on your project
+configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*).
+
+In the following example ``module`` defines the location of your bus class inside your
+package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of
+your :class:`can.BusABC` subclass.
+
+.. tab:: pyproject.toml (PEP 621)
+
+ .. code-block:: toml
+
+ # Note the quotes around can.interface in order to escape the dot .
+ [project.entry-points."can.interface"]
+ interface_name = "module:classname"
+
+.. tab:: setup.cfg
+
+ .. code-block:: ini
+
+ [options.entry_points]
+ can.interface =
+ interface_name = module:classname
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ from setuptools import setup
+
+ setup(
+ # ...,
+ entry_points = {
+ 'can.interface': [
+ 'interface_name = module:classname'
+ ]
+ }
+ )
+
+The ``interface_name`` can be used to
+create an instance of the bus in the **python-can** API:
+
+.. code-block:: python
+
+ import can
+
+ bus = can.Bus(interface="interface_name", channel=0)
+
diff --git a/doc/scripts.rst b/doc/scripts.rst
index 6b9bdf504..5a615afa7 100644
--- a/doc/scripts.rst
+++ b/doc/scripts.rst
@@ -1,7 +1,7 @@
Scripts
=======
-The following modules are callable from python-can.
+The following modules are callable from ``python-can``.
They can be called for example by ``python -m can.logger`` or ``can_logger.py`` (if installed using pip).
diff --git a/doc/utils.rst b/doc/utils.rst
new file mode 100644
index 000000000..a87d411a9
--- /dev/null
+++ b/doc/utils.rst
@@ -0,0 +1,7 @@
+Utilities
+---------
+
+
+.. autofunction:: can.detect_available_configs
+
+
diff --git a/doc/virtual-interfaces.rst b/doc/virtual-interfaces.rst
new file mode 100644
index 000000000..70ac601fa
--- /dev/null
+++ b/doc/virtual-interfaces.rst
@@ -0,0 +1,77 @@
+
+.. _virtual_interfaces_doc:
+
+Virtual Interfaces
+==================
+
+There are quite a few implementations for CAN networks that do not require physical
+CAN hardware. The built in virtual interfaces are:
+
+.. toctree::
+ :maxdepth: 1
+
+ interfaces/virtual
+ interfaces/udp_multicast
+
+
+Comparison
+----------
+
+The following table compares some known virtual interfaces:
+
++----------------------------------------------------+-----------------------------------------------------------------------+---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+
+| **Name** | **Availability** | **Applicability** | **Implementation** |
+| | +-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+| | | **Within | **Between | **Via (IP) | **Without Central | **Transport | **Serialization |
+| | | Process** | Processes** | Networks** | Server** | Technology** | Format** |
++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+| ``virtual`` (this) | *included* | ✓ | ✗ | ✗ | ✓ | Singleton & Mutex | none |
+| | | | | | | (reliable) | |
++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+| ``udp_multicast`` (:ref:`doc `) | *included* | ✓ | ✓ | ✓ | ✓ | UDP via IP multicast | custom using `msgpack `__ |
+| | | | | | | (unreliable) | |
++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+| *christiansandberg/ | `external `__ | ✓ | ✓ | ✓ | ✗ | Websockets via TCP/IP | custom binary |
+| python-can-remote* | | | | | | (reliable) | |
++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+| *windelbouwman/ | `external `__ | ✓ | ✓ | ✓ | ✗ | `ZeroMQ `__ via TCP/IP | custom binary [#f1]_ |
+| virtualcan* | | | | | | (reliable) | |
++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+
+
+.. [#f1]
+ The only option in this list that implements interoperability with other languages
+ out of the box. For the others (except the first intra-process one), other programs written
+ in potentially different languages could effortlessly interface with the bus
+ once they mimic the serialization format. The last one, however, has already implemented
+ the entire bus functionality in *C++* and *Rust*, besides the Python variant.
+
+Common Limitations
+------------------
+
+**Guaranteed delivery** and **message ordering** is one major point of difference:
+While in a physical CAN network, a message is either sent or in queue (or an explicit error occurred),
+this may not be the case for virtual networks.
+The ``udp_multicast`` bus for example, drops this property for the benefit of lower
+latencies by using unreliable UDP/IP instead of reliable TCP/IP (and because normal IP multicast
+is inherently unreliable, as the recipients are unknown by design). The other three buses faithfully
+model a physical CAN network in this regard: They ensure that all recipients actually receive
+(and acknowledge each message), much like in a physical CAN network. They also ensure that
+messages are relayed in the order they have arrived at the central server and that messages
+arrive at the recipients exactly once. Both is not guaranteed to hold for the best-effort
+``udp_multicast`` bus as it uses UDP/IP as a transport layer.
+
+**Central servers** are, however, required by interfaces 3 and 4 (the external tools) to provide
+these guarantees of message delivery and message ordering. The central servers receive and distribute
+the CAN messages to all other bus participants, unlike in a real physical CAN network.
+The first intra-process ``virtual`` interface only runs within one Python process, effectively the
+Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server.
+Notably the ``udp_multicast`` bus does not require a central server.
+
+**Arbitration and throughput** are two interrelated functions/properties of CAN networks which
+are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount
+of messages can be sent per unit of time (given the computational power of the machines and
+networks that are involved). In a real CAN/CAN FD networks, however, throughput is usually much
+more restricted and prioritization of arbitration IDs is thus an important feature once the bus
+is starting to get saturated. None of the interfaces presented above support any sort of throttling
+or ID arbitration under high loads.
+
diff --git a/examples/print_notifier.py b/examples/print_notifier.py
new file mode 100755
index 000000000..bbead7d15
--- /dev/null
+++ b/examples/print_notifier.py
@@ -0,0 +1,19 @@
+import time
+import can
+
+
+def main():
+
+ with can.Bus(receive_own_messages=True) as bus:
+ print_listener = can.Printer()
+ can.Notifier(bus, [print_listener])
+
+ bus.send(can.Message(arbitration_id=1, is_extended_id=True))
+ bus.send(can.Message(arbitration_id=2, is_extended_id=True))
+ bus.send(can.Message(arbitration_id=1, is_extended_id=False))
+
+ time.sleep(1.0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py
index a43fbe821..d48a9d8cb 100755
--- a/examples/vcan_filtered.py
+++ b/examples/vcan_filtered.py
@@ -16,7 +16,7 @@ def main():
can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}]
bus.set_filters(can_filters)
- # print all incoming messages, wich includes the ones sent,
+ # print all incoming messages, which includes the ones sent,
# since we set receive_own_messages to True
# assign to some variable so it does not garbage collected
notifier = can.Notifier(bus, [can.Printer()]) # pylint: disable=unused-variable
From 71ba9dd24ccf179b8ff0f474c1bd1ddc0ed85a04 Mon Sep 17 00:00:00 2001
From: szlegp
Date: Mon, 21 Nov 2022 23:33:43 +0100
Subject: [PATCH 166/475] Comment about bus.shutdown call in README.rst
---
README.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.rst b/README.rst
index cd2731696..a558957de 100644
--- a/README.rst
+++ b/README.rst
@@ -89,7 +89,8 @@ Example usage
# import the library
import can
- # create a bus instance
+ # create a bus instance using 'with' statement,
+ # this will cause bus.shutdown() to be called on the block exit;
# many other interfaces are supported as well (see documentation)
with can.Bus(interface='socketcan',
channel='vcan0',
From 50b1709e77b7b50a92a5f565fe8891918acd3bd9 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 22 Nov 2022 11:46:57 +0100
Subject: [PATCH 167/475] Use furo html theme for documentation (#1443)
---
doc/conf.py | 3 +--
doc/doc-requirements.txt | 2 +-
doc/interfaces/vector.rst | 4 +++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/doc/conf.py b/doc/conf.py
index c1409b8c2..de08fc300 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -49,7 +49,6 @@
"sphinx.ext.graphviz",
"sphinxcontrib.programoutput",
"sphinx_inline_tabs",
- "sphinx_rtd_theme",
]
# Now, you can use the alias name as a new role, e.g. :issue:`123`.
@@ -139,7 +138,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = "sphinx_rtd_theme"
+html_theme = "furo"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt
index b1c2da632..c61b55683 100644
--- a/doc/doc-requirements.txt
+++ b/doc/doc-requirements.txt
@@ -1,4 +1,4 @@
sphinx>=5.2.3
sphinxcontrib-programoutput
-sphinx_rtd_theme
sphinx-inline-tabs
+furo
diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst
index 56961e7e2..d3e2bed45 100644
--- a/doc/interfaces/vector.rst
+++ b/doc/interfaces/vector.rst
@@ -15,7 +15,9 @@ the bus or in a config file.
Channel should be given as a list of channels starting at 0.
Here is an example configuration file connecting to CAN 1 and CAN 2 for an
-application named "python-can"::
+application named "python-can":
+
+::
[default]
interface = vector
From 7ca2aad4d09d73a34a72266f620005c10a2d1fee Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Tue, 15 Nov 2022 19:13:03 +1300
Subject: [PATCH 168/475] Editing changelog for 4.1.0
---
CHANGELOG.md | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04ffb9b57..fc0fb84b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
Version 4.1.0
====
+Breaking Changes
+----------------
+
+* ``windows-curses`` was moved to optional dependencies (#1395).
+ Use ``pip install python-can[viewer]`` if you are using the ``can.viewer``
+ script on Windows.
+* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed
+ from camelCase to snake_case (#1422).
+
+
Features
--------
@@ -14,6 +24,7 @@ Features
Currently only the blf-, canutils- and csv-formats are supported.
* All CLI ``extra_args`` are passed to the bus, logger
and player initialisation (#1366).
+* Initial support for TRC files (#1217)
### Type Annotations
* python-can now includes the ``py.typed`` marker to support type checking
@@ -63,15 +74,7 @@ Miscellaneous
* Exclude repository-configuration from git-archive (#1343)
* Improve documentation (#1397, #1401, #1405, #1420, #1421)
* Officially support Python 3.11 (#1423)
-
-Breaking Changes
-----------------
-
-* ``windows-curses`` was moved to optional dependencies (#1395).
- Use ``pip install python-can[viewer]`` if you are using the ``can.viewer``
- script on Windows.
-* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed
- from camelCase to snake_case (#1422).
+* Migrate code coverage reporting from Codecov to Coveralls (#1430)
Version 4.0.0
====
From 488454561a03bea952fef7da7f93f9cbbce912aa Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Tue, 15 Nov 2022 19:24:34 +1300
Subject: [PATCH 169/475] Bump version for alpha release of 4.1.0
---
can/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/__init__.py b/can/__init__.py
index 8af42009a..68a309e31 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.1.0.dev0"
+__version__ = "4.1.0a0"
log = logging.getLogger("can")
From e07142d423b1c97fdc050cd6912da8a7a07d367d Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Tue, 15 Nov 2022 20:28:54 +1300
Subject: [PATCH 170/475] Remove codecov config file
---
.codecov.yml | 21 ---------------------
1 file changed, 21 deletions(-)
delete mode 100644 .codecov.yml
diff --git a/.codecov.yml b/.codecov.yml
deleted file mode 100644
index b9b9b52b6..000000000
--- a/.codecov.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-# Validate with curl --data-binary @.codecov.yml https://codecov.io/validate
-codecov:
- archive:
- uploads: yes
-
-coverage:
- precision: 2
- round: down
- range: 50...100
- status:
- project:
- default:
- # coverage may fall by <1.0% and still be considered "passing"
- threshold: 1.0%
- patch:
- default:
- # coverage may fall by <1.0% and still be considered "passing"
- threshold: 1.0%
-
-comment:
- layout: "header, diff, changes"
From 3ea6eee5089289dcae97d3f49ab5e6924f87ebc4 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:06:31 +1300
Subject: [PATCH 171/475] Get github actions to publish releases to pypi
---
.github/workflows/{build.yml => ci.yml} | 22 ++++++++++++++++++++++
.travis.yml | 24 ------------------------
requirements-lint.txt | 6 +++---
3 files changed, 25 insertions(+), 27 deletions(-)
rename .github/workflows/{build.yml => ci.yml} (86%)
diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml
similarity index 86%
rename from .github/workflows/build.yml
rename to .github/workflows/ci.yml
index 2ed1742c6..0162b4d95 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/ci.yml
@@ -143,3 +143,25 @@ jobs:
run: pipx run build
- name: Check build artifacts
run: pipx run twine check --strict dist/*
+ - name: Save artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: python-can-dist
+ path: ./dist
+
+ upload_pypi:
+ needs: [build]
+ runs-on: ubuntu-latest
+
+ # upload to PyPI only on release
+ if: github.event.release && github.event.action == 'published'
+ steps:
+ - uses: actions/download-artifact@v3
+ with:
+ name: python-can-dist
+ path: dist
+
+ - uses: pypa/gh-action-pypi-publish@v1.4.2
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
index fb24f8e9a..9bd1920e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -63,27 +63,3 @@ jobs:
travis_retry sudo pip install tox
# Run the tests
sudo tox -e travis
-
- - stage: documentation
- name: "Sphinx Build"
- python: "3.9"
- before_install:
- - travis_retry pip install -r doc/doc-requirements.txt
- script:
- # Build the docs with Sphinx
- # -a Write all files
- # -n nitpicky
- - python -m sphinx -an doc build
- - stage: deploy
- name: "PyPi Deployment"
- python: "3.9"
- deploy:
- provider: pypi
- distributions: "sdist bdist_wheel"
- user: hardbyte
- password:
- secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY=
- on:
- # Have travis deploy tagged commits to PyPi
- tags: true
- skip_cleanup: true
diff --git a/requirements-lint.txt b/requirements-lint.txt
index f62eeb189..92af74fa0 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,5 +1,5 @@
-pylint==2.12.2
-black~=22.3.0
-mypy==0.931
+pylint==2.15.5
+black~=22.10.0
+mypy==0.991
mypy-extensions==0.4.3
types-setuptools
From f697eb5bdd3ee77464b44b15d21a84e7321f1c31 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:08:54 +1300
Subject: [PATCH 172/475] Run the CI on releases, pull requests and every push
to develop
---
.github/workflows/ci.yml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0162b4d95..b97d05332 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,7 +1,11 @@
name: Tests
-on: [push, pull_request]
-
+on:
+ release:
+ types: [ published ]
+ pull_request:
+ push:
+ branches: [ develop, main ]
env:
PY_COLORS: "1"
From 717be7e31a6dbdb8c33fc0a95da26429531e86ff Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:09:57 +1300
Subject: [PATCH 173/475] Bump version to 4.1.0a1
---
can/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/__init__.py b/can/__init__.py
index 68a309e31..2398f4fdc 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.1.0a0"
+__version__ = "4.1.0a1"
log = logging.getLogger("can")
From 786bdbfce940167cc5448a6ee59168cb60f10ce4 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:18:40 +1300
Subject: [PATCH 174/475] Fix mypy's complaints
---
can/interfaces/vector/canlib.py | 2 +-
can/io/player.py | 2 +-
can/viewer.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 8f699f3f7..6abb26d40 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -28,7 +28,7 @@
INFINITE: Optional[int]
try:
# Try builtin Python 3 Windows API
- from _winapi import WaitForSingleObject, INFINITE
+ from _winapi import WaitForSingleObject, INFINITE # type: ignore
HAS_EVENTS = True
except ImportError:
diff --git a/can/io/player.py b/can/io/player.py
index 82f851502..21d1964bb 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -106,7 +106,7 @@ def decompress(
return real_suffix, gzip.open(filename, mode)
def __iter__(self) -> typing.Generator[Message, None, None]:
- pass
+ raise NotImplementedError()
class MessageSync: # pylint: disable=too-few-public-methods
diff --git a/can/viewer.py b/can/viewer.py
index 6773a0acd..202f8d546 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -390,7 +390,7 @@ def _fill_text(self, text, width, indent):
return super()._fill_text(text, width, indent)
-def parse_args(args):
+def parse_args(args: List[str]) -> Tuple:
# Parse command line arguments
parser = argparse.ArgumentParser(
"python -m can.viewer",
From a963fccb17db04a4ee0150a50feee3f337abfd13 Mon Sep 17 00:00:00 2001
From: hardbyte
Date: Wed, 16 Nov 2022 10:19:13 +0000
Subject: [PATCH 175/475] Format code with black
---
can/interfaces/vector/canlib.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 6abb26d40..fea23fb54 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -28,7 +28,7 @@
INFINITE: Optional[int]
try:
# Try builtin Python 3 Windows API
- from _winapi import WaitForSingleObject, INFINITE # type: ignore
+ from _winapi import WaitForSingleObject, INFINITE # type: ignore
HAS_EVENTS = True
except ImportError:
From 16ab05042937fd1cab12c58f917155b3ec8af75e Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:27:54 +1300
Subject: [PATCH 176/475] Remove pylint unrecognized option
---
.pylintrc | 6 ------
1 file changed, 6 deletions(-)
diff --git a/.pylintrc b/.pylintrc
index 8144d5d8f..a42935e6a 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -395,12 +395,6 @@ max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,
- dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
From a2cb2adec70b5e433c2660e1dad7f6485f695904 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:31:33 +1300
Subject: [PATCH 177/475] Don't install in local virtual env before running
pylint
---
.github/workflows/ci.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b97d05332..5ea3076f2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -71,7 +71,6 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -e .
pip install -r requirements-lint.txt
- name: mypy 3.7
run: |
From 1a1b119637d2d8d4bcf984ab0718b32fd6491509 Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Wed, 16 Nov 2022 23:58:35 +1300
Subject: [PATCH 178/475] Revert pylint version bump
---
requirements-lint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 92af74fa0..2952103c3 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,4 +1,4 @@
-pylint==2.15.5
+pylint==2.12.2
black~=22.10.0
mypy==0.991
mypy-extensions==0.4.3
From 9f68b7af921a92b4f9af244842e2e1a41cc3bced Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Fri, 18 Nov 2022 10:26:26 +1300
Subject: [PATCH 179/475] Update examples to use default configured Bus where
it makes sense
---
examples/cyclic.py | 4 +++-
examples/receive_all.py | 11 +++++++----
examples/{virtual_can_demo.py => send_multiple.py} | 5 ++++-
3 files changed, 14 insertions(+), 6 deletions(-)
rename examples/{virtual_can_demo.py => send_multiple.py} (80%)
diff --git a/examples/cyclic.py b/examples/cyclic.py
index 573465d78..bdd69eef2 100755
--- a/examples/cyclic.py
+++ b/examples/cyclic.py
@@ -114,7 +114,9 @@ def main():
arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False
)
- with can.Bus(interface="virtual") as bus:
+ # this uses the default configuration (for example from environment variables, or a
+ # config file) see https://python-can.readthedocs.io/en/stable/configuration.html
+ with can.Bus() as bus:
bus.send(reset_msg)
simple_periodic_send(bus)
diff --git a/examples/receive_all.py b/examples/receive_all.py
index 7b94d526f..d8d8714fc 100755
--- a/examples/receive_all.py
+++ b/examples/receive_all.py
@@ -11,12 +11,15 @@
def receive_all():
"""Receives all messages and prints them to the console until Ctrl+C is pressed."""
- with can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=250000) as bus:
- # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000)
- # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000)
+ # this uses the default configuration (for example from environment variables, or a
+ # config file) see https://python-can.readthedocs.io/en/stable/configuration.html
+ with can.Bus() as bus:
# set to read-only, only supported on some interfaces
- bus.state = BusState.PASSIVE
+ try:
+ bus.state = BusState.PASSIVE
+ except NotImplementedError:
+ pass
try:
while True:
diff --git a/examples/virtual_can_demo.py b/examples/send_multiple.py
similarity index 80%
rename from examples/virtual_can_demo.py
rename to examples/send_multiple.py
index af50a87a7..240b3d1cf 100755
--- a/examples/virtual_can_demo.py
+++ b/examples/send_multiple.py
@@ -16,7 +16,10 @@ def producer(thread_id: int, message_count: int = 16) -> None:
:param thread_id: the id of the thread/process
:param message_count: the number of messages that shall be sent
"""
- with can.Bus(interface="socketcan", channel="vcan0") as bus: # type: ignore
+
+ # this uses the default configuration (for example from environment variables, or a
+ # config file) see https://python-can.readthedocs.io/en/stable/configuration.html
+ with can.Bus() as bus: # type: ignore
for i in range(message_count):
msg = can.Message(
arbitration_id=0x0CF02200 + thread_id,
From 3af52709d59dc0933e791f18211361d30bce0d6c Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Fri, 18 Nov 2022 19:28:00 +1300
Subject: [PATCH 180/475] Allow restarting of transmission tasks for socketcan
(#1440)
* Allow restarting of transmission tasks for socketcan
* Update can/interfaces/socketcan/socketcan.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/interfaces/socketcan/socketcan.py | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index 549998dc8..f7b9a9ae4 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -349,7 +349,9 @@ def __init__(
self.task_id = task_id
self._tx_setup(self.messages)
- def _tx_setup(self, messages: Sequence[Message]) -> None:
+ def _tx_setup(
+ self, messages: Sequence[Message], raise_if_task_exists: bool = True
+ ) -> None:
# Create a low level packed frame to pass to the kernel
body = bytearray()
self.flags = CAN_FD_FRAME if messages[0].is_fd else 0
@@ -363,7 +365,8 @@ def _tx_setup(self, messages: Sequence[Message]) -> None:
ival1 = 0.0
ival2 = self.period
- self._check_bcm_task()
+ if raise_if_task_exists:
+ self._check_bcm_task()
header = build_bcm_transmit_header(
self.task_id, count, ival1, ival2, self.flags, nframes=len(messages)
@@ -375,7 +378,7 @@ def _tx_setup(self, messages: Sequence[Message]) -> None:
def _check_bcm_task(self) -> None:
# Do a TX_READ on a task ID, and check if we get EINVAL. If so,
- # then we are referring to a CAN message with the existing ID
+ # then we are referring to a CAN message with an existing ID
check_header = build_bcm_header(
opcode=CAN_BCM_TX_READ,
flags=0,
@@ -387,12 +390,19 @@ def _check_bcm_task(self) -> None:
can_id=self.task_id,
nframes=0,
)
+ log.debug(
+ f"Reading properties of (cyclic) transmission task id={self.task_id}",
+ )
try:
self.bcm_socket.send(check_header)
except OSError as error:
if error.errno != errno.EINVAL:
raise can.CanOperationError("failed to check", error.errno) from error
+ else:
+ log.debug("Invalid argument - transmission task not known to kernel")
else:
+ # No exception raised - transmission task with this ID exists in kernel.
+ # Existence of an existing transmission task might not be a problem!
raise can.CanOperationError(
f"A periodic task for task ID {self.task_id} is already in progress "
"by the SocketCAN Linux layer"
@@ -438,7 +448,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None:
send_bcm(self.bcm_socket, header + body)
def start(self) -> None:
- """Start a periodic task by sending TX_SETUP message to Linux kernel.
+ """Restart a periodic task by sending TX_SETUP message to Linux kernel.
It verifies presence of the particular BCM task through sending TX_READ
message to Linux kernel prior to scheduling.
@@ -446,7 +456,7 @@ def start(self) -> None:
:raises ValueError:
If the task referenced by ``task_id`` is already running.
"""
- self._tx_setup(self.messages)
+ self._tx_setup(self.messages, raise_if_task_exists=False)
class MultiRateCyclicSendTask(CyclicSendTask):
From e44833442819411ff725e434682007eb7cf7b18d Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Fri, 18 Nov 2022 19:34:07 +1300
Subject: [PATCH 181/475] 4.1.0-a2 version bump
---
can/__init__.py | 2 +-
can/logger.py | 15 ---------------
2 files changed, 1 insertion(+), 16 deletions(-)
diff --git a/can/__init__.py b/can/__init__.py
index 2398f4fdc..edaf7278b 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.1.0a1"
+__version__ = "4.1.0a2"
log = logging.getLogger("can")
diff --git a/can/logger.py b/can/logger.py
index 8cb201987..f13b78bfc 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -1,18 +1,3 @@
-"""
-logger.py logs CAN traffic to the terminal and to a file on disk.
-
- logger.py can0
-
-See candump in the can-utils package for a C implementation.
-Efficient filtering has been implemented for the socketcan backend.
-For example the command
-
- logger.py can0 F03000:FFF000
-
-Will filter for can frames with a can_id containing XXF03XXX.
-
-Dynamic Controls 2010
-"""
import re
import sys
import argparse
From 881606150316f3fce37b5605ea2cbdd4620a811f Mon Sep 17 00:00:00 2001
From: Brian Thorne
Date: Mon, 21 Nov 2022 10:26:46 +1300
Subject: [PATCH 182/475] Update changelog and set version to 4.1.0
---
CHANGELOG.md | 4 +++-
can/__init__.py | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc0fb84b3..c6ff2bbbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@ Bug Fixes
* Fix file name for compressed files in SizedRotatingLogger (#1382, #1683).
* Fix memory leak in neoVI bus where message_receipts grows with no limit (#1427).
* Raise ValueError if gzip is used with incompatible log formats (#1429).
+* Allow restarting of transmission tasks for socketcan (#1440)
Miscellaneous
-------------
@@ -72,9 +73,10 @@ Miscellaneous
* Allow ICSApiError to be pickled and un-pickled (#1341)
* Sort interface names in CLI API to make documentation reproducible (#1342)
* Exclude repository-configuration from git-archive (#1343)
-* Improve documentation (#1397, #1401, #1405, #1420, #1421)
+* Improve documentation (#1397, #1401, #1405, #1420, #1421, #1434)
* Officially support Python 3.11 (#1423)
* Migrate code coverage reporting from Codecov to Coveralls (#1430)
+* Migrate building docs and publishing releases to PyPi from Travis-CI to GitHub Actions (#1433)
Version 4.0.0
====
diff --git a/can/__init__.py b/can/__init__.py
index edaf7278b..773e94022 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -8,7 +8,7 @@
import logging
from typing import Dict, Any
-__version__ = "4.1.0a2"
+__version__ = "4.1.0"
log = logging.getLogger("can")
From 9f5af3fafd8c8b80e82ad5d6e390c62ac7682784 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Thu, 24 Nov 2022 21:00:52 +0100
Subject: [PATCH 183/475] Run pyupgrade --py37-plus (#1447)
* Run pyupgrade --py37-plus
* Format code with black
* Fix useless dict comprehension
* Add missing type hint
Co-authored-by: felixdivo
---
can/interfaces/ics_neovi/neovi_bus.py | 14 +++++++-------
can/interfaces/ixxat/canlib_vcinpl.py | 6 +++---
can/interfaces/ixxat/canlib_vcinpl2.py | 4 ++--
can/interfaces/ixxat/structures.py | 2 +-
can/interfaces/kvaser/canlib.py | 4 ++--
can/interfaces/nixnet.py | 2 +-
can/interfaces/pcan/basic.py | 2 +-
can/interfaces/pcan/pcan.py | 6 +++---
can/interfaces/robotell.py | 4 ++--
can/interfaces/socketcan/socketcan.py | 4 ++--
can/interfaces/socketcan/utils.py | 4 ++--
can/interfaces/socketcand/socketcand.py | 2 +-
can/interfaces/systec/ucanbus.py | 2 +-
can/interfaces/vector/canlib.py | 2 +-
can/io/canutils.py | 2 +-
can/io/trc.py | 6 ++----
can/typechecking.py | 19 +++++++++++++------
can/util.py | 4 ++--
can/viewer.py | 2 +-
setup.py | 4 ++--
test/config.py | 4 ++--
test/listener_test.py | 4 ++--
test/logformats_test.py | 2 +-
test/message_helper.py | 8 ++++----
test/test_interface_virtual.py | 1 -
test/test_load_config.py | 4 ++--
test/test_load_file_config.py | 4 ++--
test/test_message_class.py | 4 ++--
test/test_message_sync.py | 2 +-
test/test_viewer.py | 2 +-
30 files changed, 67 insertions(+), 63 deletions(-)
diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py
index bd4fc5445..4972ed479 100644
--- a/can/interfaces/ics_neovi/neovi_bus.py
+++ b/can/interfaces/ics_neovi/neovi_bus.py
@@ -171,8 +171,8 @@ def __init__(self, channel, can_filters=None, **kwargs):
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
- logger.info("CAN Filters: {}".format(can_filters))
- logger.info("Got configuration of: {}".format(kwargs))
+ logger.info(f"CAN Filters: {can_filters}")
+ logger.info(f"Got configuration of: {kwargs}")
if "override_library_name" in kwargs:
ics.override_library_name(kwargs.get("override_library_name"))
@@ -215,12 +215,12 @@ def __init__(self, channel, can_filters=None, **kwargs):
self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False))
self._receive_own_messages = kwargs.get("receive_own_messages", True)
- self.channel_info = "%s %s CH:%s" % (
+ self.channel_info = "{} {} CH:{}".format(
self.dev.Name,
self.get_serial_number(self.dev),
self.channels,
)
- logger.info("Using device: {}".format(self.channel_info))
+ logger.info(f"Using device: {self.channel_info}")
self.rx_buffer = deque()
self.message_receipts = defaultdict(Event)
@@ -230,7 +230,7 @@ def channel_to_netid(channel_name_or_id):
try:
channel = int(channel_name_or_id)
except ValueError:
- netid = "NETID_{}".format(channel_name_or_id.upper())
+ netid = f"NETID_{channel_name_or_id.upper()}"
if hasattr(ics, netid):
channel = getattr(ics, netid)
else:
@@ -298,9 +298,9 @@ def _find_device(self, type_filter=None, serial=None):
msg = ["No device"]
if type_filter is not None:
- msg.append("with type {}".format(type_filter))
+ msg.append(f"with type {type_filter}")
if serial is not None:
- msg.append("with serial {}".format(serial))
+ msg.append(f"with serial {serial}")
msg.append("found.")
raise CanInitializationError(" ".join(msg))
diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py
index fa88e5f90..d74da2539 100644
--- a/can/interfaces/ixxat/canlib_vcinpl.py
+++ b/can/interfaces/ixxat/canlib_vcinpl.py
@@ -119,7 +119,7 @@ def __check_status(result, function, args):
result = ctypes.c_ulong(result).value
if result == constants.VCI_E_TIMEOUT:
- raise VCITimeout("Function {} timed out".format(function._name))
+ raise VCITimeout(f"Function {function._name} timed out")
elif result == constants.VCI_E_RXQUEUE_EMPTY:
raise VCIRxQueueEmptyError()
elif result == constants.VCI_E_NO_MORE_ITEMS:
@@ -469,7 +469,7 @@ def __init__(
channel = int(channel)
if bitrate not in self.CHANNEL_BITRATES[0]:
- raise ValueError("Invalid bitrate {}".format(bitrate))
+ raise ValueError(f"Invalid bitrate {bitrate}")
if rx_fifo_size <= 0:
raise ValueError("rx_fifo_size must be > 0")
@@ -887,7 +887,7 @@ def _format_can_status(status_flags: int):
status_flags &= ~flag
if status_flags:
- states.append("unknown state 0x{:02x}".format(status_flags))
+ states.append(f"unknown state 0x{status_flags:02x}")
if states:
return "CAN status message: {}".format(", ".join(states))
diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py
index 108ad2c02..2e3125e9b 100644
--- a/can/interfaces/ixxat/canlib_vcinpl2.py
+++ b/can/interfaces/ixxat/canlib_vcinpl2.py
@@ -117,7 +117,7 @@ def __check_status(result, function, args):
:class:VCIError
"""
if result == constants.VCI_E_TIMEOUT:
- raise VCITimeout("Function {} timed out".format(function._name))
+ raise VCITimeout(f"Function {function._name} timed out")
elif result == constants.VCI_E_RXQUEUE_EMPTY:
raise VCIRxQueueEmptyError()
elif result == constants.VCI_E_NO_MORE_ITEMS:
@@ -1011,7 +1011,7 @@ def _format_can_status(status_flags: int):
status_flags &= ~flag
if status_flags:
- states.append("unknown state 0x{:02x}".format(status_flags))
+ states.append(f"unknown state 0x{status_flags:02x}")
if states:
return "CAN status message: {}".format(", ".join(states))
diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py
index b784437e0..419a52973 100644
--- a/can/interfaces/ixxat/structures.py
+++ b/can/interfaces/ixxat/structures.py
@@ -162,7 +162,7 @@ class CANMSG(ctypes.Structure):
]
def __str__(self) -> str:
- return """ID: 0x{0:04x}{1} DLC: {2:02d} DATA: {3}""".format(
+ return """ID: 0x{:04x}{} DLC: {:02d} DATA: {}""".format(
self.dwMsgId,
"[RTR]" if self.uMsgInfo.Bits.rtr else "",
self.uMsgInfo.Bits.dlc,
diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py
index f60a43bc5..a8bb7bac7 100644
--- a/can/interfaces/kvaser/canlib.py
+++ b/can/interfaces/kvaser/canlib.py
@@ -410,8 +410,8 @@ def __init__(self, channel, can_filters=None, **kwargs):
"""
- log.info("CAN Filters: {}".format(can_filters))
- log.info("Got configuration of: {}".format(kwargs))
+ log.info(f"CAN Filters: {can_filters}")
+ log.info(f"Got configuration of: {kwargs}")
bitrate = kwargs.get("bitrate", 500000)
tseg1 = kwargs.get("tseg1", 0)
tseg2 = kwargs.get("tseg2", 0)
diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py
index f1def62ad..1eba09b31 100644
--- a/can/interfaces/nixnet.py
+++ b/can/interfaces/nixnet.py
@@ -124,7 +124,7 @@ def __init__(
) from None
self._is_filtered = False
- super(NiXNETcanBus, self).__init__(
+ super().__init__(
channel=channel,
can_filters=can_filters,
bitrate=bitrate,
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index 38ded44f6..743fb55ee 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -663,7 +663,7 @@ def __init__(self):
try:
aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers")
winreg.CloseKey(aKey)
- except WindowsError:
+ except OSError:
logger.error("Exception: The PEAK-driver couldn't be found!")
finally:
winreg.CloseKey(aReg)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index c60b9e6c9..cd0349c99 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -218,7 +218,7 @@ def __init__(
channel = self._find_channel_by_dev_id(device_id)
if channel is None:
- err_msg = "Cannot find a channel with ID {:08x}".format(device_id)
+ err_msg = f"Cannot find a channel with ID {device_id:08x}"
raise ValueError(err_msg)
self.channel_info = str(channel)
@@ -249,7 +249,7 @@ def __init__(
f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None))
fd_parameters_values = [f_clock] + [
- "{}={}".format(key, kwargs.get(key, None))
+ f"{key}={kwargs.get(key, None)}"
for key in PCAN_FD_PARAMETER_LIST
if kwargs.get(key, None) is not None
]
@@ -350,7 +350,7 @@ def bits(n):
for b in bits(error):
stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9)
if stsReturn[0] != PCAN_ERROR_OK:
- text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format(
+ text = "An error occurred. Error-code's text ({:X}h) couldn't be retrieved".format(
error
)
else:
diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py
index 0d3ad1b77..4d82c1922 100644
--- a/can/interfaces/robotell.py
+++ b/can/interfaces/robotell.py
@@ -396,7 +396,7 @@ def get_serial_number(self, timeout: Optional[int]) -> Optional[str]:
serial = ""
for idx in range(0, 8, 2):
- serial += "{:02X}{:02X}-".format(sn1[idx], sn1[idx + 1])
+ serial += f"{sn1[idx]:02X}{sn1[idx + 1]:02X}-"
for idx in range(0, 4, 2):
- serial += "{:02X}{:02X}-".format(sn2[idx], sn2[idx + 1])
+ serial += f"{sn2[idx]:02X}{sn2[idx + 1]:02X}-"
return serial[:-1]
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index f7b9a9ae4..f0545f7df 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -68,7 +68,7 @@ def bcm_header_factory(
# requirements of this field, then we must add padding bytes until we
# are aligned
while curr_stride % field_alignment != 0:
- results.append(("pad_{}".format(pad_index), ctypes.c_uint8))
+ results.append((f"pad_{pad_index}", ctypes.c_uint8))
pad_index += 1
curr_stride += 1
@@ -84,7 +84,7 @@ def bcm_header_factory(
# Add trailing padding to align to a multiple of the largest scalar member
# in the structure
while curr_stride % alignment != 0:
- results.append(("pad_{}".format(pad_index), ctypes.c_uint8))
+ results.append((f"pad_{pad_index}", ctypes.c_uint8))
pad_index += 1
curr_stride += 1
diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py
index 55e7eb392..ecc870ca4 100644
--- a/can/interfaces/socketcan/utils.py
+++ b/can/interfaces/socketcan/utils.py
@@ -21,7 +21,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes
# Pass all messages
can_filters = [{"can_id": 0, "can_mask": 0}]
- can_filter_fmt = "={}I".format(2 * len(can_filters))
+ can_filter_fmt = f"={2 * len(can_filters)}I"
filter_data = []
for can_filter in can_filters:
can_id = can_filter["can_id"]
@@ -50,7 +50,7 @@ def find_available_interfaces() -> Iterable[str]:
try:
# adding "type vcan" would exclude physical can devices
command = ["ip", "-o", "link", "list", "up"]
- output = subprocess.check_output(command, universal_newlines=True)
+ output = subprocess.check_output(command, text=True)
except Exception as e: # subprocess.CalledProcessError is too specific
log.error("failed to fetch opened can devices: %s", e)
diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py
index 327df9e73..28f0c700f 100644
--- a/can/interfaces/socketcand/socketcand.py
+++ b/can/interfaces/socketcand/socketcand.py
@@ -43,7 +43,7 @@ def convert_can_message_to_ascii_message(can_message: can.Message) -> str:
# Note: seems like we cannot add CANFD_BRS (bitrate_switch) and CANFD_ESI (error_state_indicator) flags
data = can_message.data
length = can_message.dlc
- bytes_string = " ".join("{:x}".format(x) for x in data[0:length])
+ bytes_string = " ".join(f"{x:x}" for x in data[0:length])
return f"< send {can_id:X} {length:X} {bytes_string} >"
diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py
index fee110b08..7d8b6133a 100644
--- a/can/interfaces/systec/ucanbus.py
+++ b/can/interfaces/systec/ucanbus.py
@@ -134,7 +134,7 @@ def __init__(self, channel, can_filters=None, **kwargs):
self._ucan.init_hardware(device_number=device_number)
self._ucan.init_can(self.channel, **self._params)
hw_info_ex, _, _ = self._ucan.get_hardware_info()
- self.channel_info = "%s, S/N %s, CH %s, BTR %s" % (
+ self.channel_info = "{}, S/N {}, CH {}, BTR {}".format(
self._ucan.get_product_code_message(hw_info_ex.product_code),
hw_info_ex.serial,
self.channel,
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index fea23fb54..ff72d262a 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -171,7 +171,7 @@ def __init__(
)
self._app_name = app_name.encode() if app_name is not None else b""
- self.channel_info = "Application %s: %s" % (
+ self.channel_info = "Application {}: {}".format(
app_name,
", ".join(f"CAN {ch + 1}" for ch in self.channels),
)
diff --git a/can/io/canutils.py b/can/io/canutils.py
index f63df3f6b..e159ecdf4 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -170,7 +170,7 @@ def on_message_received(self, msg):
if isinstance(channel, int) or isinstance(channel, str) and channel.isdigit():
channel = f"can{channel}"
- framestr = "(%f) %s" % (timestamp, channel)
+ framestr = f"({timestamp:f}) {channel}"
if msg.is_error_frame:
framestr += " %08X#" % (CAN_ERR_FLAG | CAN_ERR_BUSERROR)
diff --git a/can/io/trc.py b/can/io/trc.py
index 25ba0f5c6..0a07f01c9 100644
--- a/can/io/trc.py
+++ b/can/io/trc.py
@@ -1,5 +1,3 @@
-# coding: utf-8
-
"""
Reader and writer for can logging files in peak trc format
@@ -51,7 +49,7 @@ def __init__(
If this is a file-like object, is has to opened in text
read mode, not binary read mode.
"""
- super(TRCReader, self).__init__(file, mode="r")
+ super().__init__(file, mode="r")
self.file_version = TRCFileVersion.UNKNOWN
if not self.file:
@@ -211,7 +209,7 @@ def __init__(
:param channel: a default channel to use when the message does not
have a channel set
"""
- super(TRCWriter, self).__init__(file, mode="w")
+ super().__init__(file, mode="w")
self.channel = channel
if type(file) is str:
self.filepath = os.path.abspath(file)
diff --git a/can/typechecking.py b/can/typechecking.py
index b3a513a3a..31a298995 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -11,9 +11,14 @@
CanFilter: typing_extensions = typing_extensions.TypedDict(
"CanFilter", {"can_id": int, "can_mask": int}
)
-CanFilterExtended = typing_extensions.TypedDict(
- "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool}
-)
+
+
+class CanFilterExtended(typing_extensions.TypedDict):
+ can_id: int
+ can_mask: int
+ extended: bool
+
+
CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]]
# TODO: Once buffer protocol support lands in typing, we should switch to that,
@@ -35,8 +40,10 @@
BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any])
-AutoDetectedConfig = typing_extensions.TypedDict(
- "AutoDetectedConfig", {"interface": str, "channel": Channel}
-)
+
+class AutoDetectedConfig(typing_extensions.TypedDict):
+ interface: str
+ channel: Channel
+
ReadableBytesLike = typing.Union[bytes, bytearray, memoryview]
diff --git a/can/util.py b/can/util.py
index e64eb13b5..41467542d 100644
--- a/can/util.py
+++ b/can/util.py
@@ -61,10 +61,10 @@ def load_file_config(
else:
config.read(path)
- _config = {}
+ _config: Dict[str, str] = {}
if config.has_section(section):
- _config.update(dict((key, val) for key, val in config.items(section)))
+ _config.update(config.items(section))
return _config
diff --git a/can/viewer.py b/can/viewer.py
index 202f8d546..e87684485 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -515,7 +515,7 @@ def parse_args(args: List[str]) -> Tuple:
] = {}
if parsed_args.decode:
if os.path.isfile(parsed_args.decode[0]):
- with open(parsed_args.decode[0], "r", encoding="utf-8") as f:
+ with open(parsed_args.decode[0], encoding="utf-8") as f:
structs = f.readlines()
else:
structs = parsed_args.decode
diff --git a/setup.py b/setup.py
index a32471844..b3d99f612 100644
--- a/setup.py
+++ b/setup.py
@@ -15,12 +15,12 @@
logging.basicConfig(level=logging.WARNING)
-with open("can/__init__.py", "r", encoding="utf-8") as fd:
+with open("can/__init__.py", encoding="utf-8") as fd:
version = re.search(
r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE
).group(1)
-with open("README.rst", "r", encoding="utf-8") as f:
+with open("README.rst", encoding="utf-8") as f:
long_description = f.read()
# Dependencies
diff --git a/test/config.py b/test/config.py
index fc1bca6c2..d308a8cc8 100644
--- a/test/config.py
+++ b/test/config.py
@@ -27,7 +27,7 @@ def env(name: str) -> bool:
IS_CI = IS_TRAVIS or IS_GITHUB_ACTIONS or env("CI") or env("CONTINUOUS_INTEGRATION")
if IS_TRAVIS and IS_GITHUB_ACTIONS:
- raise EnvironmentError(
+ raise OSError(
f"only one of IS_TRAVIS ({IS_TRAVIS}) and IS_GITHUB_ACTIONS ({IS_GITHUB_ACTIONS}) may be True at the "
"same time"
)
@@ -43,7 +43,7 @@ def env(name: str) -> bool:
del _sys
if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX):
- raise EnvironmentError(
+ raise OSError(
f"only one of IS_WINDOWS ({IS_WINDOWS}), IS_LINUX ({IS_LINUX}) and IS_OSX ({IS_OSX}) "
f'can be True at the same time (platform.system() == "{platform.system()}")'
)
diff --git a/test/listener_test.py b/test/listener_test.py
index 0e64a266a..9b2e9e93b 100644
--- a/test/listener_test.py
+++ b/test/listener_test.py
@@ -88,7 +88,7 @@ def testRemoveListenerFromNotifier(self):
def testPlayerTypeResolution(self):
def test_filetype_to_instance(extension, klass):
- print("testing: {}".format(extension))
+ print(f"testing: {extension}")
try:
if extension == ".blf":
delete = False
@@ -123,7 +123,7 @@ def testPlayerTypeResolutionUnsupportedFileTypes(self):
def testLoggerTypeResolution(self):
def test_filetype_to_instance(extension, klass):
- print("testing: {}".format(extension))
+ print(f"testing: {extension}")
try:
with tempfile.NamedTemporaryFile(
suffix=extension, delete=False
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 435b651b6..05c8b986f 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -141,7 +141,7 @@ def _setup_instance_helper(
]
assert (
"log_event" in attrs
- ), "cannot check comments with this writer: {}".format(writer_constructor)
+ ), f"cannot check comments with this writer: {writer_constructor}"
# get all test comments
self.original_comments = TEST_COMMENTS if check_comments else ()
diff --git a/test/message_helper.py b/test/message_helper.py
index 1e7989156..fe193097b 100644
--- a/test/message_helper.py
+++ b/test/message_helper.py
@@ -31,8 +31,8 @@ def assertMessageEqual(self, message_1, message_2):
if message_1.equals(message_2, timestamp_delta=self.allowed_timestamp_delta):
return
elif self.preserves_channel:
- print("Comparing: message 1: {!r}".format(message_1))
- print(" message 2: {!r}".format(message_2))
+ print(f"Comparing: message 1: {message_1!r}")
+ print(f" message 2: {message_2!r}")
self.fail(
"messages are unequal with allowed timestamp delta {}".format(
self.allowed_timestamp_delta
@@ -46,8 +46,8 @@ def assertMessageEqual(self, message_1, message_2):
):
return
else:
- print("Comparing: message 1: {!r}".format(message_1))
- print(" message 2: {!r}".format(message_2))
+ print(f"Comparing: message 1: {message_1!r}")
+ print(f" message 2: {message_2!r}")
self.fail(
"messages are unequal with allowed timestamp delta {} even when ignoring channels".format(
self.allowed_timestamp_delta
diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py
index 009722779..94833fdcb 100644
--- a/test/test_interface_virtual.py
+++ b/test/test_interface_virtual.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# coding: utf-8
"""
This module tests :meth:`can.interface.virtual`.
diff --git a/test/test_load_config.py b/test/test_load_config.py
index a2969b0a5..1bfba450a 100644
--- a/test/test_load_config.py
+++ b/test/test_load_config.py
@@ -30,9 +30,9 @@ def _gen_configration_file(self, sections):
) as tmp_config_file:
content = []
for section in sections:
- content.append("[{}]".format(section))
+ content.append(f"[{section}]")
for k, v in self.configuration[section].items():
- content.append("{} = {}".format(k, v))
+ content.append(f"{k} = {v}")
tmp_config_file.write("\n".join(content))
return tmp_config_file.name
diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py
index 6b1d5a382..afedf58d4 100644
--- a/test/test_load_file_config.py
+++ b/test/test_load_file_config.py
@@ -30,9 +30,9 @@ def _gen_configration_file(self, sections):
) as tmp_config_file:
content = []
for section in sections:
- content.append("[{}]".format(section))
+ content.append(f"[{section}]")
for k, v in self.configuration[section].items():
- content.append("{} = {}".format(k, v))
+ content.append(f"{k} = {v}")
tmp_config_file.write("\n".join(content))
return tmp_config_file.name
diff --git a/test/test_message_class.py b/test/test_message_class.py
index 9fae7262e..4840402ff 100644
--- a/test/test_message_class.py
+++ b/test/test_message_class.py
@@ -87,8 +87,8 @@ def test_methods(self, **kwargs):
if is_valid:
self.assertEqual(len(message), kwargs["dlc"])
self.assertTrue(bool(message))
- self.assertGreater(len("{}".format(message)), 0)
- _ = "{}".format(message)
+ self.assertGreater(len(f"{message}"), 0)
+ _ = f"{message}"
with self.assertRaises(Exception):
_ = "{somespec}".format(
message
diff --git a/test/test_message_sync.py b/test/test_message_sync.py
index 8750dd416..7552915e7 100644
--- a/test/test_message_sync.py
+++ b/test/test_message_sync.py
@@ -87,7 +87,7 @@ def test_skip(self):
# the handling of the messages itself also takes some time:
# ~0.001 s/message on a ThinkPad T560 laptop (Ubuntu 18.04, i5-6200U)
- assert 0 < took < inc(len(messages) * (0.005 + 0.003)), "took: {}s".format(took)
+ assert 0 < took < inc(len(messages) * (0.005 + 0.003)), f"took: {took}s"
self.assertMessagesEqual(messages, collected)
diff --git a/test/test_viewer.py b/test/test_viewer.py
index 5633a3bc1..baef10bda 100644
--- a/test/test_viewer.py
+++ b/test/test_viewer.py
@@ -302,7 +302,7 @@ def pack_data(
return struct_t.pack(*data)
else:
- raise ValueError("Unknown command: 0x{:02X}".format(cmd))
+ raise ValueError(f"Unknown command: 0x{cmd:02X}")
def test_pack_unpack(self):
CANOPEN_TPDO1 = 0x180
From cc7f81e8b7d07d58ee44fcd0f1641b0fbaeb97f6 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 26 Nov 2022 23:47:31 +0100
Subject: [PATCH 184/475] Use high resolution timer on Windows (#1449)
* use high resolution timer
* enable CI for feature branches
---
.github/workflows/ci.yml | 2 +-
can/broadcastmanager.py | 10 +++++++++-
setup.py | 2 +-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5ea3076f2..577ccd97d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,7 +5,7 @@ on:
types: [ published ]
pull_request:
push:
- branches: [ develop, main ]
+
env:
PY_COLORS: "1"
diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py
index 239d1d7d5..cfa4c658d 100644
--- a/can/broadcastmanager.py
+++ b/can/broadcastmanager.py
@@ -248,7 +248,15 @@ def __init__(
if HAS_EVENTS:
self.period_ms = int(round(period * 1000, 0))
- self.event = win32event.CreateWaitableTimer(None, False, None)
+ try:
+ self.event = win32event.CreateWaitableTimerEx(
+ None,
+ None,
+ win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
+ win32event.TIMER_ALL_ACCESS,
+ )
+ except (AttributeError, OSError):
+ self.event = win32event.CreateWaitableTimer(None, False, None)
self.start()
diff --git a/setup.py b/setup.py
index b3d99f612..dc4c7e11f 100644
--- a/setup.py
+++ b/setup.py
@@ -92,7 +92,7 @@
"setuptools",
"wrapt~=1.10",
"typing_extensions>=3.10.0.0",
- 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"',
+ 'pywin32>=305;platform_system=="Windows" and platform_python_implementation=="CPython"',
'msgpack~=1.0.0;platform_system!="Windows"',
"packaging",
],
From 486dafb3fd01f428d030ec2c4bf5d944fd77a146 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?uml=C3=A4ute?=
Date: Tue, 29 Nov 2022 22:51:16 +0100
Subject: [PATCH 185/475] executable examples (#1452)
* Fix permissions of executable scripts
* Add shebang to examples/print_notifier.py
- it's executable
- it has a __name__=='__main__' section
* dos2unix
* make sure all python-files have a consistent line-ending
* make sure all ASC-files have a consistent line-ending
---
can/exceptions.py | 244 +++++++++++++++---------------
examples/crc.py | 168 ++++++++++----------
examples/cyclic_multiple.py | 0
examples/print_notifier.py | 2 +
test/data/logfile.asc | 78 +++++-----
test/data/logfile_errorframes.asc | 42 ++---
test/test_interface_ixxat.py | 128 ++++++++--------
7 files changed, 332 insertions(+), 330 deletions(-)
mode change 100644 => 100755 examples/crc.py
mode change 100644 => 100755 examples/cyclic_multiple.py
diff --git a/can/exceptions.py b/can/exceptions.py
index dc08be3b8..7496c6c0e 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -1,122 +1,122 @@
-"""
-There are several specific :class:`Exception` classes to allow user
-code to react to specific scenarios related to CAN busses::
-
- Exception (Python standard library)
- +-- ...
- +-- CanError (python-can)
- +-- CanInterfaceNotImplementedError
- +-- CanInitializationError
- +-- CanOperationError
- +-- CanTimeoutError
-
-Keep in mind that some functions and methods may raise different exceptions.
-For example, validating typical arguments and parameters might result in a
-:class:`ValueError`. This should always be documented for the function at hand.
-"""
-
-import sys
-from contextlib import contextmanager
-
-from typing import Optional
-from typing import Type
-
-if sys.version_info >= (3, 9):
- from collections.abc import Generator
-else:
- from typing import Generator
-
-
-class CanError(Exception):
- """Base class for all CAN related exceptions.
-
- If specified, the error code is automatically appended to the message:
-
- >>> # With an error code (it also works with a specific error):
- >>> error = CanOperationError(message="Failed to do the thing", error_code=42)
- >>> str(error)
- 'Failed to do the thing [Error Code 42]'
- >>>
- >>> # Missing the error code:
- >>> plain_error = CanError(message="Something went wrong ...")
- >>> str(plain_error)
- 'Something went wrong ...'
-
- :param error_code:
- An optional error code to narrow down the cause of the fault
-
- :arg error_code:
- An optional error code to narrow down the cause of the fault
- """
-
- def __init__(
- self,
- message: str = "",
- error_code: Optional[int] = None,
- ) -> None:
- self.error_code = error_code
- super().__init__(
- message if error_code is None else f"{message} [Error Code {error_code}]"
- )
-
-
-class CanInterfaceNotImplementedError(CanError, NotImplementedError):
- """Indicates that the interface is not supported on the current platform.
-
- Example scenarios:
- - No interface with that name exists
- - The interface is unsupported on the current operating system or interpreter
- - The driver could not be found or has the wrong version
- """
-
-
-class CanInitializationError(CanError):
- """Indicates an error the occurred while initializing a :class:`can.BusABC`.
-
- If initialization fails due to a driver or platform missing/being unsupported,
- a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead.
- If initialization fails due to a value being out of range, a :class:`ValueError`
- is raised.
-
- Example scenarios:
- - Try to open a non-existent device and/or channel
- - Try to use an invalid setting, which is ok by value, but not ok for the interface
- - The device or other resources are already used
- """
-
-
-class CanOperationError(CanError):
- """Indicates an error while in operation.
-
- Example scenarios:
- - A call to a library function results in an unexpected return value
- - An invalid message was received
- - The driver rejected a message that was meant to be sent
- - Cyclic redundancy check (CRC) failed
- - A message remained unacknowledged
- - A buffer is full
- """
-
-
-class CanTimeoutError(CanError, TimeoutError):
- """Indicates the timeout of an operation.
-
- Example scenarios:
- - Some message could not be sent after the timeout elapsed
- - No message was read within the given time
- """
-
-
-@contextmanager
-def error_check(
- error_message: Optional[str] = None,
- exception_type: Type[CanError] = CanOperationError,
-) -> Generator[None, None, None]:
- """Catches any exceptions and turns them into the new type while preserving the stack trace."""
- try:
- yield
- except Exception as error: # pylint: disable=broad-except
- if error_message is None:
- raise exception_type(str(error)) from error
- else:
- raise exception_type(error_message) from error
+"""
+There are several specific :class:`Exception` classes to allow user
+code to react to specific scenarios related to CAN busses::
+
+ Exception (Python standard library)
+ +-- ...
+ +-- CanError (python-can)
+ +-- CanInterfaceNotImplementedError
+ +-- CanInitializationError
+ +-- CanOperationError
+ +-- CanTimeoutError
+
+Keep in mind that some functions and methods may raise different exceptions.
+For example, validating typical arguments and parameters might result in a
+:class:`ValueError`. This should always be documented for the function at hand.
+"""
+
+import sys
+from contextlib import contextmanager
+
+from typing import Optional
+from typing import Type
+
+if sys.version_info >= (3, 9):
+ from collections.abc import Generator
+else:
+ from typing import Generator
+
+
+class CanError(Exception):
+ """Base class for all CAN related exceptions.
+
+ If specified, the error code is automatically appended to the message:
+
+ >>> # With an error code (it also works with a specific error):
+ >>> error = CanOperationError(message="Failed to do the thing", error_code=42)
+ >>> str(error)
+ 'Failed to do the thing [Error Code 42]'
+ >>>
+ >>> # Missing the error code:
+ >>> plain_error = CanError(message="Something went wrong ...")
+ >>> str(plain_error)
+ 'Something went wrong ...'
+
+ :param error_code:
+ An optional error code to narrow down the cause of the fault
+
+ :arg error_code:
+ An optional error code to narrow down the cause of the fault
+ """
+
+ def __init__(
+ self,
+ message: str = "",
+ error_code: Optional[int] = None,
+ ) -> None:
+ self.error_code = error_code
+ super().__init__(
+ message if error_code is None else f"{message} [Error Code {error_code}]"
+ )
+
+
+class CanInterfaceNotImplementedError(CanError, NotImplementedError):
+ """Indicates that the interface is not supported on the current platform.
+
+ Example scenarios:
+ - No interface with that name exists
+ - The interface is unsupported on the current operating system or interpreter
+ - The driver could not be found or has the wrong version
+ """
+
+
+class CanInitializationError(CanError):
+ """Indicates an error the occurred while initializing a :class:`can.BusABC`.
+
+ If initialization fails due to a driver or platform missing/being unsupported,
+ a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead.
+ If initialization fails due to a value being out of range, a :class:`ValueError`
+ is raised.
+
+ Example scenarios:
+ - Try to open a non-existent device and/or channel
+ - Try to use an invalid setting, which is ok by value, but not ok for the interface
+ - The device or other resources are already used
+ """
+
+
+class CanOperationError(CanError):
+ """Indicates an error while in operation.
+
+ Example scenarios:
+ - A call to a library function results in an unexpected return value
+ - An invalid message was received
+ - The driver rejected a message that was meant to be sent
+ - Cyclic redundancy check (CRC) failed
+ - A message remained unacknowledged
+ - A buffer is full
+ """
+
+
+class CanTimeoutError(CanError, TimeoutError):
+ """Indicates the timeout of an operation.
+
+ Example scenarios:
+ - Some message could not be sent after the timeout elapsed
+ - No message was read within the given time
+ """
+
+
+@contextmanager
+def error_check(
+ error_message: Optional[str] = None,
+ exception_type: Type[CanError] = CanOperationError,
+) -> Generator[None, None, None]:
+ """Catches any exceptions and turns them into the new type while preserving the stack trace."""
+ try:
+ yield
+ except Exception as error: # pylint: disable=broad-except
+ if error_message is None:
+ raise exception_type(str(error)) from error
+ else:
+ raise exception_type(error_message) from error
diff --git a/examples/crc.py b/examples/crc.py
old mode 100644
new mode 100755
index 4344fb2ea..18d22681a
--- a/examples/crc.py
+++ b/examples/crc.py
@@ -1,84 +1,84 @@
-#!/usr/bin/env python
-
-"""
-This example exercises the periodic task's multiple message sending capabilities
-to send a message containing a counter and a checksum.
-
-Expects a vcan0 interface:
-
- python3 -m examples.crc
-
-"""
-
-import logging
-import time
-
-import can
-
-logging.basicConfig(level=logging.INFO)
-
-
-def crc_send(bus):
- """
- Sends periodic messages every 1 s with no explicit timeout. Modifies messages
- after 8 seconds, sends for 10 more seconds, then stops.
- """
- msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0])
- messages = build_crc_msgs(msg)
-
- print(
- "Starting to send a message with updating counter and checksum every 1 s for 8 s"
- )
- task = bus.send_periodic(messages, 1)
- assert isinstance(task, can.CyclicSendTaskABC)
- time.sleep(8)
-
- msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0])
- messages = build_crc_msgs(msg)
-
- print("Sending modified message data every 1 s for 10 s")
- task.modify_data(messages)
- time.sleep(10)
- task.stop()
- print("stopped cyclic send")
-
-
-def build_crc_msgs(msg):
- """
- Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters
- and SPN 3188 checksums placed in the final byte.
- """
- messages = []
-
- for counter in range(16):
- checksum = compute_xbr_checksum(msg, counter)
- msg.data[7] = counter + (checksum << 4)
- messages.append(
- can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:])
- )
-
- return messages
-
-
-def compute_xbr_checksum(message, counter):
- """
- Computes an XBR checksum per SAE J1939 SPN 3188.
- """
- checksum = sum(message.data[:7])
- checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big"))
- checksum += counter & 0x0F
- xbr_checksum = ((checksum >> 4) + checksum) & 0x0F
-
- return xbr_checksum
-
-
-if __name__ == "__main__":
- for interface, channel in [("socketcan", "vcan0")]:
- print(f"Carrying out crc test with {interface} interface")
-
- with can.Bus( # type: ignore
- interface=interface, channel=channel, bitrate=500000
- ) as BUS:
- crc_send(BUS)
-
- time.sleep(2)
+#!/usr/bin/env python
+
+"""
+This example exercises the periodic task's multiple message sending capabilities
+to send a message containing a counter and a checksum.
+
+Expects a vcan0 interface:
+
+ python3 -m examples.crc
+
+"""
+
+import logging
+import time
+
+import can
+
+logging.basicConfig(level=logging.INFO)
+
+
+def crc_send(bus):
+ """
+ Sends periodic messages every 1 s with no explicit timeout. Modifies messages
+ after 8 seconds, sends for 10 more seconds, then stops.
+ """
+ msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0])
+ messages = build_crc_msgs(msg)
+
+ print(
+ "Starting to send a message with updating counter and checksum every 1 s for 8 s"
+ )
+ task = bus.send_periodic(messages, 1)
+ assert isinstance(task, can.CyclicSendTaskABC)
+ time.sleep(8)
+
+ msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0])
+ messages = build_crc_msgs(msg)
+
+ print("Sending modified message data every 1 s for 10 s")
+ task.modify_data(messages)
+ time.sleep(10)
+ task.stop()
+ print("stopped cyclic send")
+
+
+def build_crc_msgs(msg):
+ """
+ Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters
+ and SPN 3188 checksums placed in the final byte.
+ """
+ messages = []
+
+ for counter in range(16):
+ checksum = compute_xbr_checksum(msg, counter)
+ msg.data[7] = counter + (checksum << 4)
+ messages.append(
+ can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:])
+ )
+
+ return messages
+
+
+def compute_xbr_checksum(message, counter):
+ """
+ Computes an XBR checksum per SAE J1939 SPN 3188.
+ """
+ checksum = sum(message.data[:7])
+ checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big"))
+ checksum += counter & 0x0F
+ xbr_checksum = ((checksum >> 4) + checksum) & 0x0F
+
+ return xbr_checksum
+
+
+if __name__ == "__main__":
+ for interface, channel in [("socketcan", "vcan0")]:
+ print(f"Carrying out crc test with {interface} interface")
+
+ with can.Bus( # type: ignore
+ interface=interface, channel=channel, bitrate=500000
+ ) as BUS:
+ crc_send(BUS)
+
+ time.sleep(2)
diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py
old mode 100644
new mode 100755
diff --git a/examples/print_notifier.py b/examples/print_notifier.py
index bbead7d15..b6554ccd2 100755
--- a/examples/print_notifier.py
+++ b/examples/print_notifier.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
import time
import can
diff --git a/test/data/logfile.asc b/test/data/logfile.asc
index 8e6ac0464..13274a4c4 100644
--- a/test/data/logfile.asc
+++ b/test/data/logfile.asc
@@ -1,39 +1,39 @@
-date Sam Sep 30 15:06:13.191 2017
-base hex timestamps absolute
-internal events logged
-// version 9.0.0
-//0.000000 previous log file: logfile_errorframes.asc
-Begin Triggerblock Sam Sep 30 15:06:13.191 2017
- 0.000000 Start of measurement
- 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
- 0.015991 CAN 2 Status:chip status error active
- 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
- 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
- 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
- 2.501000 1 ErrorFrame
- 2.501010 1 ErrorFrame ECC: 10100010
- 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
- 2.510001 2 100 Tx r
- 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
- 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
- 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x
- 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x
- 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
- 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x
- 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF
- 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0
- 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785
- 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784
- 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04%
- 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x
- 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x
- 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
- 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x
- 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF
- 30.005071 CANFD 2 Rx 300 Generic_Name_12 1 0 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d
- 30.300981 CANFD 3 Tx 50005x 0 0 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205
- 30.506898 CANFD 4 Rx 4EE 0 0 f 64 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205
- 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205
- 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
- 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
-End TriggerBlock
+date Sam Sep 30 15:06:13.191 2017
+base hex timestamps absolute
+internal events logged
+// version 9.0.0
+//0.000000 previous log file: logfile_errorframes.asc
+Begin Triggerblock Sam Sep 30 15:06:13.191 2017
+ 0.000000 Start of measurement
+ 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
+ 0.015991 CAN 2 Status:chip status error active
+ 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
+ 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
+ 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
+ 2.501000 1 ErrorFrame
+ 2.501010 1 ErrorFrame ECC: 10100010
+ 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
+ 2.510001 2 100 Tx r
+ 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
+ 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
+ 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x
+ 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x
+ 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x
+ 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF
+ 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0
+ 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785
+ 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784
+ 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04%
+ 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x
+ 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x
+ 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x
+ 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF
+ 30.005071 CANFD 2 Rx 300 Generic_Name_12 1 0 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d
+ 30.300981 CANFD 3 Tx 50005x 0 0 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205
+ 30.506898 CANFD 4 Rx 4EE 0 0 f 64 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205
+ 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205
+ 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
+ 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00%
+End TriggerBlock
diff --git a/test/data/logfile_errorframes.asc b/test/data/logfile_errorframes.asc
index bcb5584a7..6f751bfac 100644
--- a/test/data/logfile_errorframes.asc
+++ b/test/data/logfile_errorframes.asc
@@ -1,21 +1,21 @@
-date Sam Sep 30 15:06:13.191 2017
-base hex timestamps absolute
-internal events logged
-// version 9.0.0
-Begin Triggerblock Sam Sep 30 15:06:13.191 2017
- 0.000000 Start of measurement
- 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
- 0.015991 CAN 2 Status:chip status error active
- 2.501000 1 ErrorFrame
- 2.501010 1 ErrorFrame ECC: 10100010
- 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
- 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
- 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
- 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x
- 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
- 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784
- 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x
- 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x
- 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
- 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x
-End TriggerBlock
+date Sam Sep 30 15:06:13.191 2017
+base hex timestamps absolute
+internal events logged
+// version 9.0.0
+Begin Triggerblock Sam Sep 30 15:06:13.191 2017
+ 0.000000 Start of measurement
+ 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
+ 0.015991 CAN 2 Status:chip status error active
+ 2.501000 1 ErrorFrame
+ 2.501010 1 ErrorFrame ECC: 10100010
+ 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
+ 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
+ 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
+ 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x
+ 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784
+ 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x
+ 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x
+ 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x
+ 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x
+End TriggerBlock
diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py
index 76285e422..484a88b58 100644
--- a/test/test_interface_ixxat.py
+++ b/test/test_interface_ixxat.py
@@ -1,64 +1,64 @@
-#!/usr/bin/env python
-
-"""
-Unittest for ixxat interface.
-
-Run only this test:
-python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py"
-"""
-
-import unittest
-import can
-
-
-class SoftwareTestCase(unittest.TestCase):
- """
- Test cases that test the software only and do not rely on an existing/connected hardware.
- """
-
- def setUp(self):
- try:
- bus = can.Bus(interface="ixxat", channel=0)
- bus.shutdown()
- except can.CanInterfaceNotImplementedError:
- raise unittest.SkipTest("not available on this platform")
-
- def test_bus_creation(self):
- # channel must be >= 0
- with self.assertRaises(ValueError):
- can.Bus(interface="ixxat", channel=-1)
-
- # rx_fifo_size must be > 0
- with self.assertRaises(ValueError):
- can.Bus(interface="ixxat", channel=0, rx_fifo_size=0)
-
- # tx_fifo_size must be > 0
- with self.assertRaises(ValueError):
- can.Bus(interface="ixxat", channel=0, tx_fifo_size=0)
-
-
-class HardwareTestCase(unittest.TestCase):
- """
- Test cases that rely on an existing/connected hardware.
- """
-
- def setUp(self):
- try:
- bus = can.Bus(interface="ixxat", channel=0)
- bus.shutdown()
- except can.CanInterfaceNotImplementedError:
- raise unittest.SkipTest("not available on this platform")
-
- def test_bus_creation(self):
- # non-existent channel -> use arbitrary high value
- with self.assertRaises(can.CanInitializationError):
- can.Bus(interface="ixxat", channel=0xFFFF)
-
- def test_send_after_shutdown(self):
- with can.Bus(interface="ixxat", channel=0) as bus:
- with self.assertRaises(can.CanOperationError):
- bus.send(can.Message(arbitration_id=0x3FF, dlc=0))
-
-
-if __name__ == "__main__":
- unittest.main()
+#!/usr/bin/env python
+
+"""
+Unittest for ixxat interface.
+
+Run only this test:
+python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py"
+"""
+
+import unittest
+import can
+
+
+class SoftwareTestCase(unittest.TestCase):
+ """
+ Test cases that test the software only and do not rely on an existing/connected hardware.
+ """
+
+ def setUp(self):
+ try:
+ bus = can.Bus(interface="ixxat", channel=0)
+ bus.shutdown()
+ except can.CanInterfaceNotImplementedError:
+ raise unittest.SkipTest("not available on this platform")
+
+ def test_bus_creation(self):
+ # channel must be >= 0
+ with self.assertRaises(ValueError):
+ can.Bus(interface="ixxat", channel=-1)
+
+ # rx_fifo_size must be > 0
+ with self.assertRaises(ValueError):
+ can.Bus(interface="ixxat", channel=0, rx_fifo_size=0)
+
+ # tx_fifo_size must be > 0
+ with self.assertRaises(ValueError):
+ can.Bus(interface="ixxat", channel=0, tx_fifo_size=0)
+
+
+class HardwareTestCase(unittest.TestCase):
+ """
+ Test cases that rely on an existing/connected hardware.
+ """
+
+ def setUp(self):
+ try:
+ bus = can.Bus(interface="ixxat", channel=0)
+ bus.shutdown()
+ except can.CanInterfaceNotImplementedError:
+ raise unittest.SkipTest("not available on this platform")
+
+ def test_bus_creation(self):
+ # non-existent channel -> use arbitrary high value
+ with self.assertRaises(can.CanInitializationError):
+ can.Bus(interface="ixxat", channel=0xFFFF)
+
+ def test_send_after_shutdown(self):
+ with can.Bus(interface="ixxat", channel=0) as bus:
+ with self.assertRaises(can.CanOperationError):
+ bus.send(can.Message(arbitration_id=0x3FF, dlc=0))
+
+
+if __name__ == "__main__":
+ unittest.main()
From 25d30b529dcc28cbe75320d8315b57233fd10cc1 Mon Sep 17 00:00:00 2001
From: Hugo SERRAT
Date: Wed, 7 Dec 2022 16:11:09 +0100
Subject: [PATCH 186/475] fix: canfilter typing (#1456)
Co-authored-by: hugo
---
can/typechecking.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/can/typechecking.py b/can/typechecking.py
index 31a298995..7aa4f7e5c 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -8,9 +8,10 @@
import typing_extensions
-CanFilter: typing_extensions = typing_extensions.TypedDict(
- "CanFilter", {"can_id": int, "can_mask": int}
-)
+
+class CanFilter(typing_extensions.TypedDict):
+ can_id: int
+ can_mask: int
class CanFilterExtended(typing_extensions.TypedDict):
From f3136fb5e85f11ecb9ba83744a955a706f9dbde2 Mon Sep 17 00:00:00 2001
From: MattWoodhead
Date: Mon, 19 Dec 2022 12:39:19 +0000
Subject: [PATCH 187/475] Update documentation with plugins, related tools etc.
(#1457)
* Update interfaces.rst
Add a note regarding the ability to use plugins or installing external modules to extend the functionality of python-can.
* Update plugin-interface.rst
Add examples of modules using the plugin api
* Update index.rst
* Create other-tools.rst
* Update other-tools.rst
* Add optional deps to setup.py
* Format code with black
* Update other-tools.rst
* Update plugin-interface.rst
* Update doc/other-tools.rst
fix note block
Co-authored-by: Brian Thorne
* Update doc/other-tools.rst
Co-authored-by: Brian Thorne
* Address sphinx warnings
* Update doc/interfaces.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update doc/interfaces.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update doc/other-tools.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update doc/plugin-interface.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update doc/interfaces.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Update doc/plugin-interface.rst
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
* Remove extra installation tag out of scope of PR
* Fix numbering & spelling
Fix numbering of lists. Correct careless spelling errors.
Co-authored-by: MattWoodhead
Co-authored-by: Brian Thorne
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
doc/index.rst | 1 +
doc/interfaces.rst | 4 +-
doc/other-tools.rst | 79 ++++++++++++++++++++++++++++++++++++++++
doc/plugin-interface.rst | 23 ++++++++++++
setup.py | 3 ++
5 files changed, 109 insertions(+), 1 deletion(-)
create mode 100644 doc/other-tools.rst
diff --git a/doc/index.rst b/doc/index.rst
index 505c8b87b..c55108d97 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -45,6 +45,7 @@ Contents:
interfaces
virtual-interfaces
plugin-interface
+ other-tools
scripts
development
history
diff --git a/doc/interfaces.rst b/doc/interfaces.rst
index cc686d2d5..6645ec338 100644
--- a/doc/interfaces.rst
+++ b/doc/interfaces.rst
@@ -12,7 +12,7 @@ documentation.
The *Interface Names* are listed in :doc:`configuration`.
-The available hardware interfaces are:
+The following hardware interfaces are included in python-can:
.. toctree::
:maxdepth: 1
@@ -39,3 +39,5 @@ The available hardware interfaces are:
interfaces/usb2can
interfaces/vector
+
+Additional interface types can be added via the :ref:`plugin interface`, or by installing a third party package that utilises the :ref:`plugin interface`.
diff --git a/doc/other-tools.rst b/doc/other-tools.rst
new file mode 100644
index 000000000..56ec21083
--- /dev/null
+++ b/doc/other-tools.rst
@@ -0,0 +1,79 @@
+Other CAN bus tools
+===================
+
+In order to keep the project maintainable, the scope of the package is limited to providing common
+abstractions to different hardware devices, and a basic suite of utilities for sending and
+receiving messages on a CAN bus. Other tools are available that either extend the functionality
+of python-can, or provide complementary features that python-can users might find useful.
+
+Some of these tools are listed below for convenience.
+
+CAN Message protocols (implemented in Python)
+---------------------------------------------
+
+#. SAE J1939 Message Protocol
+ * The `can-j1939`_ module provides an implementation of the CAN SAE J1939 standard for Python,
+ including J1939-22. `can-j1939`_ uses python-can to provide support for multiple hardware
+ interfaces.
+#. CIA CANopen
+ * The `canopen`_ module provides an implementation of the CIA CANopen protocol, aiming to be
+ used for automation and testing purposes
+#. ISO 15765-2 (ISO TP)
+ * The `can-isotp`_ module provides an implementation of the ISO TP CAN protocol for sending
+ data packets via a CAN transport layer.
+
+#. UDS
+ * The `python-uds`_ module is a communication protocol agnostic implementation of the Unified
+ Diagnostic Services (UDS) protocol defined in ISO 14229-1, although it does have extensions
+ for performing UDS over CAN utilising the ISO TP protocol. This module has not been updated
+ for some time.
+ * The `uds`_ module is another tool that implements the UDS protocol, although it does have
+ extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not
+ been updated for some time.
+#. XCP
+ * The `pyxcp`_ module implements the Universal Measurement and Calibration Protocol (XCP).
+ The purpose of XCP is to adjust parameters and acquire current values of internal
+ variables in an ECU.
+
+.. _can-j1939: https://github.com/juergenH87/python-can-j1939
+.. _canopen: https://canopen.readthedocs.io/en/latest/
+.. _can-isotp: https://can-isotp.readthedocs.io/en/latest/
+.. _python-uds: https://python-uds.readthedocs.io/en/latest/index.html
+.. _uds: https://uds.readthedocs.io/en/latest/
+.. _pyxcp: https://pyxcp.readthedocs.io/en/latest/
+
+CAN Frame Parsing tools etc. (implemented in Python)
+----------------------------------------------------
+
+#. CAN Message / Database scripting
+ * The `cantools`_ package provides multiple methods for interacting with can message database
+ files, and using these files to monitor live busses with a command line monitor tool.
+#. CAN Message / Log Decoding
+ * The `canmatrix`_ module provides methods for converting between multiple popular message
+ frame definition file formats (e.g. .DBC files, .KCD files, .ARXML files etc.).
+ * The `pretty_j1939`_ module can be used to post-process CAN logs of J1939 traffic into human
+ readable terminal prints or into a JSON file for consumption elsewhere in your scripts.
+
+.. _cantools: https://cantools.readthedocs.io/en/latest/
+.. _canmatrix: https://canmatrix.readthedocs.io/en/latest/
+.. _pretty_j1939: https://github.com/nmfta-repo/pretty_j1939
+
+Other CAN related tools, programs etc.
+--------------------------------------
+
+#. Micropython CAN class
+ * A `CAN class`_ is available for the original micropython pyboard, with much of the same
+ functionality as is available with python-can (but with a different API!).
+#. ASAM MDF Files
+ * The `asammdf`_ module provides many methods for processing ASAM (Association for
+ Standardization of Automation and Measuring Systems) MDF (Measurement Data Format) files.
+
+.. _`CAN class`: https://docs.micropython.org/en/latest/library/pyb.CAN.html
+.. _`asammdf`: https://asammdf.readthedocs.io/en/master/
+
+|
+|
+
+.. note::
+ See also the available plugins for python-can in :ref:`plugin interface`.
+
diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst
index 14c3f51d5..bab8c85a9 100644
--- a/doc/plugin-interface.rst
+++ b/doc/plugin-interface.rst
@@ -52,3 +52,26 @@ create an instance of the bus in the **python-can** API:
bus = can.Bus(interface="interface_name", channel=0)
+
+
+Example Interface Plugins
+-------------------------
+
+The table below lists interface drivers that can be added by installing additional packages that utilise the plugin API. These modules are optional dependencies of python-can.
+
+.. note::
+ The packages listed below are maintained by other authors. Any issues should be reported in their corresponding repository and **not** in the python-can repository.
+
++----------------------------+-------------------------------------------------------+
+| Name | Description |
++============================+=======================================================+
+| `python-can-cvector`_ | Cython based version of the 'VectorBus' |
++----------------------------+-------------------------------------------------------+
+| `python-can-remote`_ | CAN over network bridge |
++----------------------------+-------------------------------------------------------+
+| `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) |
++----------------------------+-------------------------------------------------------+
+
+.. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector
+.. _python-can-remote: https://github.com/christiansandberg/python-can-remote
+.. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim
diff --git a/setup.py b/setup.py
index dc4c7e11f..bada45b77 100644
--- a/setup.py
+++ b/setup.py
@@ -30,9 +30,12 @@
"neovi": ["filelock", "python-ics>=2.12"],
"canalystii": ["canalystii>=0.1.0"],
"cantact": ["cantact>=0.0.7"],
+ "cvector": ["python-can-cvector"],
"gs_usb": ["gs_usb>=0.2.1"],
"nixnet": ["nixnet>=0.3.1"],
"pcan": ["uptime~=3.0.1"],
+ "remote": ["python-can-remote"],
+ "sontheim": ["python-can-sontheim>=0.1.2"],
"viewer": [
'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"'
],
From 4136f37c115e59d3c6cd7f6f2c9dbba706b51056 Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Wed, 21 Dec 2022 01:33:54 +0100
Subject: [PATCH 188/475] Fix Bus.__new__ for PEAK CAN-FD interfaces (#1460)
* Add failing unit test to verify that issue #1485 is fixed
https://github.com/hardbyte/python-can/issues/1458
* Remove unused generic BitTiming creation in Bus.__new__
The BitTiming class is an attempt at unifying the various timing
parameters of the individual interfaces. The idea is that instead of
manually supplying multiple parameters that make up the timing
definition of the interface, one can instead supply a single instance of
the BitTiming class, which will also automatically calculate derivative
values from its input.
At the moment, this class is only used by two interfaces: CANtact and
CANanalystii. Both either accept a single bitrate or a BitTiming
instance. The latter will overrule the former.
The code that is removed with this commit is part of the generic
Bus.__new__ constructor. The removed code searches the set of kwargs
parameters for timing-related values. If it finds at least one such
value, it creates a BitTiming class instance and adds it to the list of
parameters. However, it breaks compatibility with the PEAK interface,
see issue #1458. Additionally, the code in question is generic and
applies to all interfaces. Instantiating a class here is prone to issues
since it must be generic enough to fit all use cases. A better approach
would be to simply forward the parameters as was done previously and
leave it up to the individual interfaces to handle things properly.
* Format code with black
Co-authored-by: lumagi
---
can/util.py | 19 -------------------
test/test_pcan.py | 20 ++++++++++++++++++++
2 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/can/util.py b/can/util.py
index 41467542d..aa4a28d15 100644
--- a/can/util.py
+++ b/can/util.py
@@ -233,25 +233,6 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
if "data_bitrate" in config:
config["data_bitrate"] = int(config["data_bitrate"])
- # Create bit timing configuration if given
- timing_conf = {}
- for key in (
- "f_clock",
- "brp",
- "tseg1",
- "tseg2",
- "sjw",
- "nof_samples",
- "btr0",
- "btr1",
- ):
- if key in config:
- timing_conf[key] = int(str(config[key]), base=0)
- del config[key]
- if timing_conf:
- timing_conf["bitrate"] = config["bitrate"]
- config["timing"] = can.BitTiming(**timing_conf)
-
return cast(typechecking.BusConfig, config)
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 0a680fea0..ab03bf0a1 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -373,6 +373,26 @@ def test_auto_reset_init_fault(self):
with self.assertRaises(CanInitializationError):
self.bus = can.Bus(bustype="pcan", auto_reset=True)
+ def test_peak_fd_bus_constructor_regression(self):
+ # Tests that the following issue has been fixed:
+ # https://github.com/hardbyte/python-can/issues/1458
+ params = {
+ "interface": "pcan",
+ "fd": True,
+ "f_clock": 80000000,
+ "nom_brp": 1,
+ "nom_tseg1": 129,
+ "nom_tseg2": 30,
+ "nom_sjw": 1,
+ "data_brp": 1,
+ "data_tseg1": 9,
+ "data_tseg2": 6,
+ "data_sjw": 1,
+ "channel": "PCAN_USBBUS1",
+ }
+
+ can.Bus(**params)
+
if __name__ == "__main__":
unittest.main()
From 1046c8c50cb388e6550a93de8f5aeb053f0703e0 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:20:16 +0100
Subject: [PATCH 189/475] Tiny type narrowing
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 755e8675c..bb206e72b 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -6,7 +6,7 @@
from typing import Dict, Tuple
# interface_name => (module, classname)
-BACKENDS: Dict[str, Tuple[str, ...]] = {
+BACKENDS: Dict[str, Tuple[str, str]] = {
"kvaser": ("can.interfaces.kvaser", "KvaserBus"),
"socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
"serial": ("can.interfaces.serial.serial_can", "SerialBus"),
From be29fc97070fa2ba80f5f047aa58bf08fce86aaa Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:28:44 +0100
Subject: [PATCH 190/475] Fix "DeprecationWarning: SelectableGroups dict
interface is deprecated. Use select."
Previously, the change line issued the above deprecation warning. This code fixes it. I also tested it locally. To reproduce the error before the change, simply run `python -W error -c 'import can.interfaces'`.
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 755e8675c..8f74a114c 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -35,7 +35,7 @@
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
- entries = entry_points().get("can.interface", ())
+ entries = entry_points(group="can.interface")
BACKENDS.update(
{interface.name: tuple(interface.value.split(":")) for interface in entries}
)
From 6cb1af1d05561ccce3e7dab03b4fb66472e6883a Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:37:29 +0100
Subject: [PATCH 191/475] Add required cast
Apparently, this is required for mypy to accept it ....
---
can/interfaces/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index bb206e72b..f686f7633 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -6,7 +6,7 @@
from typing import Dict, Tuple
# interface_name => (module, classname)
-BACKENDS: Dict[str, Tuple[str, str]] = {
+BACKENDS: Dict[str, Tuple[str, str]] = cast(Dict[str, Tuple[str, str]], {
"kvaser": ("can.interfaces.kvaser", "KvaserBus"),
"socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
"serial": ("can.interfaces.serial.serial_can", "SerialBus"),
@@ -30,7 +30,7 @@
"neousys": ("can.interfaces.neousys", "NeousysBus"),
"etas": ("can.interfaces.etas", "EtasBus"),
"socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
-}
+})
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
From e2071431ab544a36c19f3c12b35033e87da8b68d Mon Sep 17 00:00:00 2001
From: felixdivo
Date: Wed, 21 Dec 2022 15:38:08 +0000
Subject: [PATCH 192/475] Format code with black
---
can/interfaces/__init__.py | 53 ++++++++++++++++++++------------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index f686f7633..5162360b9 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -6,31 +6,34 @@
from typing import Dict, Tuple
# interface_name => (module, classname)
-BACKENDS: Dict[str, Tuple[str, str]] = cast(Dict[str, Tuple[str, str]], {
- "kvaser": ("can.interfaces.kvaser", "KvaserBus"),
- "socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
- "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"),
- "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"),
- "neovi": ("can.interfaces.ics_neovi", "NeoViBus"),
- "vector": ("can.interfaces.vector", "VectorBus"),
- "slcan": ("can.interfaces.slcan", "slcanBus"),
- "robotell": ("can.interfaces.robotell", "robotellBus"),
- "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
- "systec": ("can.interfaces.systec", "UcanBus"),
- "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
- "cantact": ("can.interfaces.cantact", "CantactBus"),
- "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
- "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
- "neousys": ("can.interfaces.neousys", "NeousysBus"),
- "etas": ("can.interfaces.etas", "EtasBus"),
- "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
-})
+BACKENDS: Dict[str, Tuple[str, str]] = cast(
+ Dict[str, Tuple[str, str]],
+ {
+ "kvaser": ("can.interfaces.kvaser", "KvaserBus"),
+ "socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
+ "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"),
+ "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"),
+ "neovi": ("can.interfaces.ics_neovi", "NeoViBus"),
+ "vector": ("can.interfaces.vector", "VectorBus"),
+ "slcan": ("can.interfaces.slcan", "slcanBus"),
+ "robotell": ("can.interfaces.robotell", "robotellBus"),
+ "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
+ "systec": ("can.interfaces.systec", "UcanBus"),
+ "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
+ "cantact": ("can.interfaces.cantact", "CantactBus"),
+ "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
+ "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
+ "neousys": ("can.interfaces.neousys", "NeousysBus"),
+ "etas": ("can.interfaces.etas", "EtasBus"),
+ "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
+ },
+)
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
From 05e3283c08d0c2a1426c4a9a04c6c0fecba8eba9 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:41:15 +0100
Subject: [PATCH 193/475] Restore compatibility with Python version < 3.10
---
can/interfaces/__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 8f74a114c..f3a6e94cf 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -35,7 +35,8 @@
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
- entries = entry_points(group="can.interface")
+ # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
+ entries = entry_points(group="can.interface") if sys.version_info >= (3, 10) else entry_points().get("can.interface", ())
BACKENDS.update(
{interface.name: tuple(interface.value.split(":")) for interface in entries}
)
From 6885ca41d8fcaed8a711f5773fa6d847ec6145f4 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:42:43 +0100
Subject: [PATCH 194/475] Add missing import
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 5162360b9..edb58b228 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -3,7 +3,7 @@
"""
import sys
-from typing import Dict, Tuple
+from typing import cast, Dict, Tuple
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, str]] = cast(
From 41d8c0e0d2680e9f018279d0ba87262aa17732ae Mon Sep 17 00:00:00 2001
From: felixdivo
Date: Wed, 21 Dec 2022 15:46:01 +0000
Subject: [PATCH 195/475] Format code with black
---
can/interfaces/__init__.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index f3a6e94cf..1f115b5ad 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -36,7 +36,11 @@
from importlib.metadata import entry_points
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
- entries = entry_points(group="can.interface") if sys.version_info >= (3, 10) else entry_points().get("can.interface", ())
+ entries = (
+ entry_points(group="can.interface")
+ if sys.version_info >= (3, 10)
+ else entry_points().get("can.interface", ())
+ )
BACKENDS.update(
{interface.name: tuple(interface.value.split(":")) for interface in entries}
)
From ab58780b4b776b7738a624a407d52fe08bebb221 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 16:55:48 +0100
Subject: [PATCH 196/475] Try to fix typing
---
can/interfaces/__init__.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 1f115b5ad..093c21d81 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -3,7 +3,7 @@
"""
import sys
-from typing import Dict, Tuple
+from typing import Dict, Iterable, Tuple
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, ...]] = {
@@ -33,13 +33,13 @@
}
if sys.version_info >= (3, 8):
- from importlib.metadata import entry_points
+ from importlib.metadata import entry_points, EntryPoint
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
- entries = (
+ entries: Iterable[EntryPoint] = (
entry_points(group="can.interface")
if sys.version_info >= (3, 10)
- else entry_points().get("can.interface", ())
+ else entry_points().get("can.interface", [])
)
BACKENDS.update(
{interface.name: tuple(interface.value.split(":")) for interface in entries}
From 41a958d7fad9f6f76cd4e5868d34a5b7322b5487 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 17:04:41 +0100
Subject: [PATCH 197/475] Update __init__.py
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index edb58b228..9e03b126c 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -40,7 +40,7 @@
entries = entry_points().get("can.interface", ())
BACKENDS.update(
- {interface.name: tuple(interface.value.split(":")) for interface in entries}
+ {interface.name: (interface.module, interface.attr) for interface in entries}
)
else:
from pkg_resources import iter_entry_points
From 551e1c391bbb5b4a1334bd9edc1807b693ec3dee Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 17:19:24 +0100
Subject: [PATCH 198/475] Try to make mypy happy
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 093c21d81..2c6973b12 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -36,7 +36,7 @@
from importlib.metadata import entry_points, EntryPoint
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
- entries: Iterable[EntryPoint] = (
+ entries: Iterable[EntryPoint] = ( # type: ignore
entry_points(group="can.interface")
if sys.version_info >= (3, 10)
else entry_points().get("can.interface", [])
From f5df2733791659f2b7acd6b9ca336960e187a69a Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 17:29:22 +0100
Subject: [PATCH 199/475] Update __init__.py
---
can/interfaces/__init__.py | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index eec099294..f9aee6112 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -39,14 +39,15 @@
from importlib.metadata import entry_points, EntryPoint
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
- entries: Iterable[EntryPoint] = ( # type: ignore
- entry_points(group="can.interface")
- if sys.version_info >= (3, 10)
- else entry_points().get("can.interface", [])
- )
- BACKENDS.update(
- {interface.name: (interface.module, interface.attr) for interface in entries}
- )
+ # The second variant causes a deprecation warning on Python >= 3.10.
+ if sys.version_info >= (3, 10):
+ BACKENDS.update(
+ {interface.name: (interface.module, interface.attr) for interface in entry_points(group="can.interface")}
+ )
+ else:
+ BACKENDS.update(
+ {interface.name: tuple(interface.value.split(":")) for interface in entry_points().get("can.interface", [])}
+ )
else:
from pkg_resources import iter_entry_points
From 66a5d2a1466b060a32ce04909fd6057ad26facfd Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 21 Dec 2022 17:43:05 +0100
Subject: [PATCH 200/475] Add deprecation warning for 'bustype' parameter
(#1462)
* deprecate bustype parameter
* Add comment
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
---
can/interface.py | 3 +-
doc/bus.rst | 2 +-
doc/configuration.rst | 2 +-
doc/interfaces/gs_usb.rst | 4 +--
doc/interfaces/seeedstudio.rst | 2 +-
doc/interfaces/socketcan.rst | 4 +--
doc/interfaces/socketcand.rst | 2 +-
doc/interfaces/udp_multicast.rst | 2 +-
doc/interfaces/virtual.rst | 8 ++---
examples/asyncio_demo.py | 2 +-
examples/crc.py | 4 +--
examples/cyclic_multiple.py | 4 +--
examples/send_multiple.py | 2 +-
test/back2back_test.py | 16 ++++-----
test/simplecyclic_test.py | 12 +++----
test/test_cantact.py | 12 +++----
test/test_interface_virtual.py | 4 +--
test/test_kvaser.py | 8 ++---
test/test_message_filtering.py | 2 +-
test/test_neousys.py | 4 +--
test/test_pcan.py | 52 +++++++++++++--------------
test/test_robotell.py | 2 +-
test/test_slcan.py | 2 +-
test/test_systec.py | 18 +++++-----
test/test_vector.py | 61 +++++++++++++++++---------------
test/zero_dlc_test.py | 10 +++---
26 files changed, 122 insertions(+), 122 deletions(-)
diff --git a/can/interface.py b/can/interface.py
index 527f84d20..76d0dd1a5 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -9,7 +9,7 @@
from typing import Any, cast, Iterable, Type, Optional, Union, List
from .bus import BusABC
-from .util import load_config
+from .util import load_config, deprecated_args_alias
from .interfaces import BACKENDS
from .exceptions import CanInterfaceNotImplementedError
from .typechecking import AutoDetectedConfig, Channel
@@ -88,6 +88,7 @@ class Bus(BusABC): # pylint: disable=abstract-method
"""
@staticmethod
+ @deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2
def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
cls: Any,
channel: Optional[Channel] = None,
diff --git a/doc/bus.rst b/doc/bus.rst
index 06a740829..51ed0220b 100644
--- a/doc/bus.rst
+++ b/doc/bus.rst
@@ -69,7 +69,7 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass
{"can_id": 0x451, "can_mask": 0x7FF, "extended": False},
{"can_id": 0xA0000, "can_mask": 0x1FFFFFFF, "extended": True},
]
- bus = can.interface.Bus(channel="can0", bustype="socketcan", can_filters=filters)
+ bus = can.interface.Bus(channel="can0", interface="socketcan", can_filters=filters)
See :meth:`~can.BusABC.set_filters` for the implementation.
diff --git a/doc/configuration.rst b/doc/configuration.rst
index d92d6164f..494351350 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -30,7 +30,7 @@ You can also specify the interface and channel for each Bus instance::
import can
- bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=500000)
+ bus = can.interface.Bus(interface='socketcan', channel='vcan0', bitrate=500000)
Configuration File
diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst
index af69581be..3a869911c 100755
--- a/doc/interfaces/gs_usb.rst
+++ b/doc/interfaces/gs_usb.rst
@@ -14,7 +14,7 @@ Usage: pass device ``index`` (starting from 0) if using automatic device detecti
import can
- bus = can.Bus(bustype="gs_usb", channel=dev.product, index=0, bitrate=250000)
+ bus = can.Bus(interface="gs_usb", channel=dev.product, index=0, bitrate=250000)
Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below:
@@ -25,7 +25,7 @@ Alternatively, pass ``bus`` and ``address`` to open a specific device. The param
dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F)
bus = can.Bus(
- bustype="gs_usb",
+ interface="gs_usb",
channel=dev.product,
bus=dev.bus,
address=dev.address,
diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst
index 98ea352c5..ae07b0545 100644
--- a/doc/interfaces/seeedstudio.rst
+++ b/doc/interfaces/seeedstudio.rst
@@ -31,7 +31,7 @@ Interface
A bus example::
- bus = can.interface.Bus(bustype='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000)
+ bus = can.interface.Bus(interface='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000)
diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst
index b07f4f78e..ddc965678 100644
--- a/doc/interfaces/socketcan.rst
+++ b/doc/interfaces/socketcan.rst
@@ -177,12 +177,12 @@ To spam a bus:
import time
import can
- bustype = 'socketcan'
+ interface = 'socketcan'
channel = 'vcan0'
def producer(id):
""":param id: Spam the bus with messages including the data id."""
- bus = can.Bus(channel=channel, interface=bustype)
+ bus = can.Bus(channel=channel, interface=interface)
for i in range(10):
msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False)
bus.send(msg)
diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst
index 2f313470c..0214f094a 100644
--- a/doc/interfaces/socketcand.rst
+++ b/doc/interfaces/socketcand.rst
@@ -18,7 +18,7 @@ daemon running on a remote Raspberry Pi:
import can
- bus = can.interface.Bus(bustype='socketcand', host="10.0.16.15", port=29536, channel="can0")
+ bus = can.interface.Bus(interface='socketcand', host="10.0.16.15", port=29536, channel="can0")
# loop until Ctrl-C
try:
diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst
index b15354ed5..4f9745615 100644
--- a/doc/interfaces/udp_multicast.rst
+++ b/doc/interfaces/udp_multicast.rst
@@ -40,7 +40,7 @@ from ``bus_1`` to ``bus_2``:
from can.interfaces.udp_multicast import UdpMulticastBus
# The bus can be created using the can.Bus wrapper class or using UdpMulticastBus directly
- with can.Bus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6, bustype='udp_multicast') as bus_1, \
+ with can.Bus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6, interface='udp_multicast') as bus_1, \
UdpMulticastBus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6) as bus_2:
# register a callback on the second bus that prints messages to the standard out
diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst
index 7569ffeb9..bdadcb08d 100644
--- a/doc/interfaces/virtual.rst
+++ b/doc/interfaces/virtual.rst
@@ -18,8 +18,8 @@ Example
import can
- bus1 = can.interface.Bus('test', bustype='virtual')
- bus2 = can.interface.Bus('test', bustype='virtual')
+ bus1 = can.interface.Bus('test', interface='virtual')
+ bus2 = can.interface.Bus('test', interface='virtual')
msg1 = can.Message(arbitration_id=0xabcde, data=[1,2,3])
bus1.send(msg1)
@@ -34,8 +34,8 @@ Example
import can
- bus1 = can.interface.Bus('test', bustype='virtual', preserve_timestamps=True)
- bus2 = can.interface.Bus('test', bustype='virtual')
+ bus1 = can.interface.Bus('test', interface='virtual', preserve_timestamps=True)
+ bus2 = can.interface.Bus('test', interface='virtual')
msg1 = can.Message(timestamp=1639740470.051948, arbitration_id=0xabcde, data=[1,2,3])
diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py
index 0f37d6573..d29f03bc5 100755
--- a/examples/asyncio_demo.py
+++ b/examples/asyncio_demo.py
@@ -19,7 +19,7 @@ def print_message(msg: can.Message) -> None:
async def main() -> None:
"""The main function that runs in the loop."""
- with can.Bus( # type: ignore
+ with can.Bus(
interface="virtual", channel="my_channel_0", receive_own_messages=True
) as bus:
reader = can.AsyncBufferedReader()
diff --git a/examples/crc.py b/examples/crc.py
index 18d22681a..fff3dce25 100755
--- a/examples/crc.py
+++ b/examples/crc.py
@@ -76,9 +76,7 @@ def compute_xbr_checksum(message, counter):
for interface, channel in [("socketcan", "vcan0")]:
print(f"Carrying out crc test with {interface} interface")
- with can.Bus( # type: ignore
- interface=interface, channel=channel, bitrate=500000
- ) as BUS:
+ with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS:
crc_send(BUS)
time.sleep(2)
diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py
index 64f0862d7..43dc0cd17 100755
--- a/examples/cyclic_multiple.py
+++ b/examples/cyclic_multiple.py
@@ -133,9 +133,7 @@ def cyclic_multiple_send_modify(bus):
for interface, channel in [("socketcan", "vcan0")]:
print(f"Carrying out cyclic multiple tests with {interface} interface")
- with can.Bus( # type: ignore
- interface=interface, channel=channel, bitrate=500000
- ) as BUS:
+ with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS:
cyclic_multiple_send(BUS)
cyclic_multiple_send_modify(BUS)
diff --git a/examples/send_multiple.py b/examples/send_multiple.py
index 240b3d1cf..fdcaa5b59 100755
--- a/examples/send_multiple.py
+++ b/examples/send_multiple.py
@@ -19,7 +19,7 @@ def producer(thread_id: int, message_count: int = 16) -> None:
# this uses the default configuration (for example from environment variables, or a
# config file) see https://python-can.readthedocs.io/en/stable/configuration.html
- with can.Bus() as bus: # type: ignore
+ with can.Bus() as bus:
for i in range(message_count):
msg = can.Message(
arbitration_id=0x0CF02200 + thread_id,
diff --git a/test/back2back_test.py b/test/back2back_test.py
index ab4d57dc1..54d619878 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -43,14 +43,14 @@ class Back2BackTestCase(unittest.TestCase):
def setUp(self):
self.bus1 = can.Bus(
channel=self.CHANNEL_1,
- bustype=self.INTERFACE_1,
+ interface=self.INTERFACE_1,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
)
self.bus2 = can.Bus(
channel=self.CHANNEL_2,
- bustype=self.INTERFACE_2,
+ interface=self.INTERFACE_2,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
@@ -166,7 +166,7 @@ def test_message_is_rx_receive_own_messages(self):
"""The same as `test_message_direction` but testing with `receive_own_messages=True`."""
bus3 = can.Bus(
channel=self.CHANNEL_2,
- bustype=self.INTERFACE_2,
+ interface=self.INTERFACE_2,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
@@ -188,7 +188,7 @@ def test_unique_message_instances(self):
"""
bus3 = can.Bus(
channel=self.CHANNEL_2,
- bustype=self.INTERFACE_2,
+ interface=self.INTERFACE_2,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
@@ -347,8 +347,8 @@ def test_unique_message_instances(self):
@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan")
class SocketCanBroadcastChannel(unittest.TestCase):
def setUp(self):
- self.broadcast_bus = can.Bus(channel="", bustype="socketcan")
- self.regular_bus = can.Bus(channel="vcan0", bustype="socketcan")
+ self.broadcast_bus = can.Bus(channel="", interface="socketcan")
+ self.regular_bus = can.Bus(channel="vcan0", interface="socketcan")
def tearDown(self):
self.broadcast_bus.shutdown()
@@ -370,14 +370,14 @@ class TestThreadSafeBus(Back2BackTestCase):
def setUp(self):
self.bus1 = can.ThreadSafeBus(
channel=self.CHANNEL_1,
- bustype=self.INTERFACE_1,
+ interface=self.INTERFACE_1,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
)
self.bus2 = can.ThreadSafeBus(
channel=self.CHANNEL_2,
- bustype=self.INTERFACE_2,
+ interface=self.INTERFACE_2,
bitrate=self.BITRATE,
fd=TEST_CAN_FD,
single_handle=True,
diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py
index 4b9ded43f..639694bfa 100644
--- a/test/simplecyclic_test.py
+++ b/test/simplecyclic_test.py
@@ -31,8 +31,8 @@ def test_cycle_time(self):
is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]
)
- with can.interface.Bus(bustype="virtual") as bus1:
- with can.interface.Bus(bustype="virtual") as bus2:
+ with can.interface.Bus(interface="virtual") as bus1:
+ with can.interface.Bus(interface="virtual") as bus2:
# disabling the garbage collector makes the time readings more reliable
gc.disable()
@@ -67,7 +67,7 @@ def test_cycle_time(self):
self.assertMessageEqual(msg, last_msg)
def test_removing_bus_tasks(self):
- bus = can.interface.Bus(bustype="virtual")
+ bus = can.interface.Bus(interface="virtual")
tasks = []
for task_i in range(10):
msg = can.Message(
@@ -90,7 +90,7 @@ def test_removing_bus_tasks(self):
bus.shutdown()
def test_managed_tasks(self):
- bus = can.interface.Bus(bustype="virtual", receive_own_messages=True)
+ bus = can.interface.Bus(interface="virtual", receive_own_messages=True)
tasks = []
for task_i in range(3):
msg = can.Message(
@@ -120,7 +120,7 @@ def test_managed_tasks(self):
bus.shutdown()
def test_stopping_perodic_tasks(self):
- bus = can.interface.Bus(bustype="virtual")
+ bus = can.interface.Bus(interface="virtual")
tasks = []
for task_i in range(10):
msg = can.Message(
@@ -153,7 +153,7 @@ def test_stopping_perodic_tasks(self):
@unittest.skipIf(IS_CI, "fails randomly when run on CI server")
def test_thread_based_cyclic_send_task(self):
- bus = can.ThreadSafeBus(bustype="virtual")
+ bus = can.ThreadSafeBus(interface="virtual")
msg = can.Message(
is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]
)
diff --git a/test/test_cantact.py b/test/test_cantact.py
index e361ad1ad..4383fab37 100644
--- a/test/test_cantact.py
+++ b/test/test_cantact.py
@@ -12,7 +12,7 @@
class CantactTest(unittest.TestCase):
def test_bus_creation(self):
- bus = can.Bus(channel=0, bustype="cantact", _testing=True)
+ bus = can.Bus(channel=0, interface="cantact", _testing=True)
self.assertIsInstance(bus, cantact.CantactBus)
cantact.MockInterface.set_bitrate.assert_called()
cantact.MockInterface.set_bit_timing.assert_not_called()
@@ -24,7 +24,7 @@ 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)
+ bus = can.Bus(channel=0, interface="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()
@@ -33,7 +33,7 @@ def test_bus_creation_bittiming(self):
cantact.MockInterface.start.assert_called()
def test_transmit(self):
- bus = can.Bus(channel=0, bustype="cantact", _testing=True)
+ bus = can.Bus(channel=0, interface="cantact", _testing=True)
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -41,18 +41,18 @@ def test_transmit(self):
cantact.MockInterface.send.assert_called()
def test_recv(self):
- bus = can.Bus(channel=0, bustype="cantact", _testing=True)
+ bus = can.Bus(channel=0, interface="cantact", _testing=True)
frame = bus.recv(timeout=0.5)
cantact.MockInterface.recv.assert_called()
self.assertIsInstance(frame, can.Message)
def test_recv_timeout(self):
- bus = can.Bus(channel=0, bustype="cantact", _testing=True)
+ bus = can.Bus(channel=0, interface="cantact", _testing=True)
frame = bus.recv(timeout=0.0)
cantact.MockInterface.recv.assert_called()
self.assertIsNone(frame)
def test_shutdown(self):
- bus = can.Bus(channel=0, bustype="cantact", _testing=True)
+ bus = can.Bus(channel=0, interface="cantact", _testing=True)
bus.shutdown()
cantact.MockInterface.stop.assert_called()
diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py
index 94833fdcb..c1d842180 100644
--- a/test/test_interface_virtual.py
+++ b/test/test_interface_virtual.py
@@ -13,8 +13,8 @@
class TestMessageFiltering(unittest.TestCase):
def setUp(self):
- self.node1 = Bus("test", bustype="virtual", preserve_timestamps=True)
- self.node2 = Bus("test", bustype="virtual")
+ self.node1 = Bus("test", interface="virtual", preserve_timestamps=True)
+ self.node2 = Bus("test", interface="virtual")
def tearDown(self):
self.node1.shutdown()
diff --git a/test/test_kvaser.py b/test/test_kvaser.py
index 0efdfd643..fda8b8316 100644
--- a/test/test_kvaser.py
+++ b/test/test_kvaser.py
@@ -37,7 +37,7 @@ def setUp(self):
self.msg = {}
self.msg_in_cue = None
- self.bus = can.Bus(channel=0, bustype="kvaser")
+ self.bus = can.Bus(channel=0, interface="kvaser")
def tearDown(self):
if self.bus:
@@ -149,7 +149,7 @@ def test_available_configs(self):
def test_canfd_default_data_bitrate(self):
canlib.canSetBusParams.reset_mock()
canlib.canSetBusParamsFd.reset_mock()
- can.Bus(channel=0, bustype="kvaser", fd=True)
+ can.Bus(channel=0, interface="kvaser", fd=True)
canlib.canSetBusParams.assert_called_once_with(
0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0
)
@@ -161,7 +161,7 @@ def test_canfd_nondefault_data_bitrate(self):
canlib.canSetBusParams.reset_mock()
canlib.canSetBusParamsFd.reset_mock()
data_bitrate = 2000000
- can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate)
+ can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate)
bitrate_constant = canlib.BITRATE_FD[data_bitrate]
canlib.canSetBusParams.assert_called_once_with(
0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0
@@ -172,7 +172,7 @@ def test_canfd_custom_data_bitrate(self):
canlib.canSetBusParams.reset_mock()
canlib.canSetBusParamsFd.reset_mock()
data_bitrate = 123456
- can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate)
+ can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate)
canlib.canSetBusParams.assert_called_once_with(
0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0
)
diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py
index addea13fd..e6fe16d46 100644
--- a/test/test_message_filtering.py
+++ b/test/test_message_filtering.py
@@ -21,7 +21,7 @@
class TestMessageFiltering(unittest.TestCase):
def setUp(self):
- self.bus = Bus(bustype="virtual", channel="testy")
+ self.bus = Bus(interface="virtual", channel="testy")
def tearDown(self):
self.bus.shutdown()
diff --git a/test/test_neousys.py b/test/test_neousys.py
index f61c37655..26a220048 100644
--- a/test/test_neousys.py
+++ b/test/test_neousys.py
@@ -33,7 +33,7 @@ def setUp(self) -> None:
can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Start = Mock(return_value=1)
can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Send = Mock(return_value=1)
can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Stop = Mock(return_value=1)
- self.bus = can.Bus(channel=0, bustype="neousys")
+ self.bus = can.Bus(channel=0, interface="neousys")
def tearDown(self) -> None:
if self.bus:
@@ -66,7 +66,7 @@ def test_bus_creation(self) -> None:
)
def test_bus_creation_bitrate(self) -> None:
- self.bus = can.Bus(channel=0, bustype="neousys", bitrate=200000)
+ self.bus = can.Bus(channel=0, interface="neousys", bitrate=200000)
self.assertIsInstance(self.bus, neousys.NeousysBus)
CAN_Start_args = (
can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0]
diff --git a/test/test_pcan.py b/test/test_pcan.py
index ab03bf0a1..7e8e27cf6 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -49,7 +49,7 @@ def _mockGetValue(self, channel, parameter):
)
def test_bus_creation(self) -> None:
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
self.mock_pcan.Initialize.assert_called_once()
@@ -57,10 +57,10 @@ def test_bus_creation(self) -> None:
def test_bus_creation_state_error(self) -> None:
with self.assertRaises(ValueError):
- can.Bus(bustype="pcan", state=BusState.ERROR)
+ can.Bus(interface="pcan", state=BusState.ERROR)
def test_bus_creation_fd(self) -> None:
- self.bus = can.Bus(bustype="pcan", fd=True)
+ self.bus = can.Bus(interface="pcan", fd=True)
self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
self.mock_pcan.Initialize.assert_not_called()
@@ -69,7 +69,7 @@ def test_bus_creation_fd(self) -> None:
def test_api_version_low(self) -> None:
self.PCAN_API_VERSION_SIM = "1.0"
with self.assertLogs("can.pcan", level="WARNING") as cm:
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
found_version_warning = False
for i in cm.output:
if "version" in i and "pcan" in i:
@@ -82,7 +82,7 @@ def test_api_version_low(self) -> None:
def test_api_version_read_fail(self) -> None:
self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None))
with self.assertRaises(CanInitializationError):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
@parameterized.expand(
[
@@ -98,7 +98,7 @@ def test_api_version_read_fail(self) -> None:
)
def test_get_formatted_error(self, name, status1, status2, expected_result: str):
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.mock_pcan.GetErrorText = Mock(
side_effect=[
(status1, expected_result.encode("utf-8", errors="replace")),
@@ -111,7 +111,7 @@ def test_get_formatted_error(self, name, status1, status2, expected_result: str)
self.assertEqual(complete_text, expected_result)
def test_status(self) -> None:
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.bus.status()
self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1)
@@ -121,7 +121,7 @@ def test_status(self) -> None:
def test_status_is_ok(self, name, status, expected_result) -> None:
with self.subTest(name):
self.mock_pcan.GetStatus = Mock(return_value=status)
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.assertEqual(self.bus.status_is_ok(), expected_result)
self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1)
@@ -131,7 +131,7 @@ def test_status_is_ok(self, name, status, expected_result) -> None:
def test_reset(self, name, status, expected_result) -> None:
with self.subTest(name):
self.mock_pcan.Reset = Mock(return_value=status)
- self.bus = can.Bus(bustype="pcan", fd=True)
+ self.bus = can.Bus(interface="pcan", fd=True)
self.assertEqual(self.bus.reset(), expected_result)
self.mock_pcan.Reset.assert_called_once_with(PCAN_USBBUS1)
@@ -140,7 +140,7 @@ def test_reset(self, name, status, expected_result) -> None:
)
def test_get_device_number(self, name, status, expected_result) -> None:
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan", fd=True)
+ self.bus = can.Bus(interface="pcan", fd=True)
# Mock GetValue after creation of bus to use first mock of
# GetValue in constructor
self.mock_pcan.GetValue = Mock(return_value=(status, 1))
@@ -155,7 +155,7 @@ def test_get_device_number(self, name, status, expected_result) -> None:
)
def test_set_device_number(self, name, status, expected_result) -> None:
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.mock_pcan.SetValue = Mock(return_value=status)
self.assertEqual(self.bus.set_device_number(3), expected_result)
# check last SetValue call
@@ -170,7 +170,7 @@ def test_recv(self):
timestamp = TPCANTimestamp()
self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp))
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
recv_msg = self.bus.recv()
self.assertEqual(recv_msg.arbitration_id, msg.ID)
@@ -193,7 +193,7 @@ def test_recv_fd(self):
self.mock_pcan.ReadFD = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp))
- self.bus = can.Bus(bustype="pcan", fd=True)
+ self.bus = can.Bus(interface="pcan", fd=True)
recv_msg = self.bus.recv()
self.assertEqual(recv_msg.arbitration_id, msg.ID)
@@ -206,12 +206,12 @@ def test_recv_fd(self):
@pytest.mark.timeout(3.0)
def test_recv_no_message(self):
self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None))
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.assertEqual(self.bus.recv(timeout=0.5), None)
def test_send(self) -> None:
self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK)
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -221,7 +221,7 @@ def test_send(self) -> None:
def test_send_fd(self) -> None:
self.mock_pcan.WriteFD = Mock(return_value=PCAN_ERROR_OK)
- self.bus = can.Bus(bustype="pcan", fd=True)
+ self.bus = can.Bus(interface="pcan", fd=True)
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -269,7 +269,7 @@ def test_send_type(self, name, msg_type, expected_value) -> None:
self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK)
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
msg = can.Message(
arbitration_id=0xC0FFEF,
data=[1, 2, 3, 4, 5, 6, 7, 8],
@@ -287,7 +287,7 @@ def test_send_type(self, name, msg_type, expected_value) -> None:
def test_send_error(self) -> None:
self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_BUSHEAVY)
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -298,7 +298,7 @@ def test_send_error(self) -> None:
@parameterized.expand([("on", True), ("off", False)])
def test_flash(self, name, flash) -> None:
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.bus.flash(flash)
call_list = self.mock_pcan.SetValue.call_args_list
last_call_args_list = call_list[-1][0]
@@ -307,7 +307,7 @@ def test_flash(self, name, flash) -> None:
)
def test_shutdown(self) -> None:
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.bus.shutdown()
self.mock_pcan.Uninitialize.assert_called_once_with(PCAN_USBBUS1)
@@ -319,7 +319,7 @@ def test_shutdown(self) -> None:
)
def test_state(self, name, bus_state: BusState, expected_parameter) -> None:
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.bus.state = bus_state
call_list = self.mock_pcan.SetValue.call_args_list
@@ -339,7 +339,7 @@ def test_detect_available_configs(self) -> None:
@parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)])
def test_status_string(self, name, status, expected_result) -> None:
with self.subTest(name):
- self.bus = can.Bus(bustype="pcan")
+ self.bus = can.Bus(interface="pcan")
self.mock_pcan.GetStatus = Mock(return_value=status)
self.assertEqual(self.bus.status_string(), expected_result)
self.mock_pcan.GetStatus.assert_called()
@@ -358,20 +358,20 @@ def get_value_side_effect(handle, param):
self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect)
if expected_result == "error":
- self.assertRaises(ValueError, can.Bus, bustype="pcan", device_id=dev_id)
+ self.assertRaises(ValueError, can.Bus, interface="pcan", device_id=dev_id)
else:
- self.bus = can.Bus(bustype="pcan", device_id=dev_id)
+ self.bus = can.Bus(interface="pcan", device_id=dev_id)
self.assertEqual(expected_result, self.bus.channel_info)
def test_bus_creation_auto_reset(self):
- self.bus = can.Bus(bustype="pcan", auto_reset=True)
+ self.bus = can.Bus(interface="pcan", auto_reset=True)
self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
def test_auto_reset_init_fault(self):
self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_INITIALIZE)
with self.assertRaises(CanInitializationError):
- self.bus = can.Bus(bustype="pcan", auto_reset=True)
+ self.bus = can.Bus(interface="pcan", auto_reset=True)
def test_peak_fd_bus_constructor_regression(self):
# Tests that the following issue has been fixed:
diff --git a/test/test_robotell.py b/test/test_robotell.py
index 8250b7ada..64f4acaf1 100644
--- a/test/test_robotell.py
+++ b/test/test_robotell.py
@@ -7,7 +7,7 @@
class robotellTestCase(unittest.TestCase):
def setUp(self):
# will log timeout messages since we are not feeding ack messages to the serial port at this stage
- self.bus = can.Bus("loop://", bustype="robotell")
+ self.bus = can.Bus("loop://", interface="robotell")
self.serial = self.bus.serialPortOrig
self.serial.read(self.serial.in_waiting)
diff --git a/test/test_slcan.py b/test/test_slcan.py
index 1e6282d41..aa97e518b 100644
--- a/test/test_slcan.py
+++ b/test/test_slcan.py
@@ -6,7 +6,7 @@
class slcanTestCase(unittest.TestCase):
def setUp(self):
- self.bus = can.Bus("loop://", bustype="slcan", sleep_after_open=0)
+ self.bus = can.Bus("loop://", interface="slcan", sleep_after_open=0)
self.serial = self.bus.serialPortOrig
self.serial.read(self.serial.in_waiting)
diff --git a/test/test_systec.py b/test/test_systec.py
index 5e8b30dcf..7495f75eb 100644
--- a/test/test_systec.py
+++ b/test/test_systec.py
@@ -32,7 +32,7 @@ def setUp(self):
ucan.UcanWriteCanMsgEx = Mock()
ucan.UcanResetCanEx = Mock()
ucan._UCAN_INITIALIZED = True # Fake this
- self.bus = can.Bus(bustype="systec", channel=0, bitrate=125000)
+ self.bus = can.Bus(interface="systec", channel=0, bitrate=125000)
def test_bus_creation(self):
self.assertIsInstance(self.bus, ucanbus.UcanBus)
@@ -136,7 +136,7 @@ def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending):
@staticmethod
def test_bus_defaults():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0)
+ bus = can.Bus(interface="systec", channel=0)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
@@ -155,7 +155,7 @@ def test_bus_defaults():
@staticmethod
def test_bus_channel():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=1)
+ bus = can.Bus(interface="systec", channel=1)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
1,
@@ -174,7 +174,7 @@ def test_bus_channel():
@staticmethod
def test_bus_bitrate():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0, bitrate=125000)
+ bus = can.Bus(interface="systec", channel=0, bitrate=125000)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
@@ -192,12 +192,12 @@ def test_bus_bitrate():
def test_bus_custom_bitrate(self):
with self.assertRaises(ValueError):
- can.Bus(bustype="systec", channel=0, bitrate=123456)
+ can.Bus(interface="systec", channel=0, bitrate=123456)
@staticmethod
def test_receive_own_messages():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0, receive_own_messages=True)
+ bus = can.Bus(interface="systec", channel=0, receive_own_messages=True)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
@@ -216,7 +216,7 @@ def test_receive_own_messages():
@staticmethod
def test_bus_passive_state():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0, state=can.BusState.PASSIVE)
+ bus = can.Bus(interface="systec", channel=0, state=can.BusState.PASSIVE)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
@@ -235,7 +235,7 @@ def test_bus_passive_state():
@staticmethod
def test_rx_buffer_entries():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0, rx_buffer_entries=1024)
+ bus = can.Bus(interface="systec", channel=0, rx_buffer_entries=1024)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
@@ -254,7 +254,7 @@ def test_rx_buffer_entries():
@staticmethod
def test_tx_buffer_entries():
ucan.UcanInitCanEx2.reset_mock()
- bus = can.Bus(bustype="systec", channel=0, tx_buffer_entries=1024)
+ bus = can.Bus(interface="systec", channel=0, tx_buffer_entries=1024)
ucan.UcanInitCanEx2.assert_called_once_with(
bus._ucan._handle,
0,
diff --git a/test/test_vector.py b/test/test_vector.py
index b0f305821..21125cc18 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -77,7 +77,7 @@ def mock_xldriver() -> None:
def test_bus_creation_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
assert isinstance(bus, canlib.VectorBus)
can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
@@ -93,7 +93,7 @@ def test_bus_creation_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_bus_creation() -> None:
- bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector")
assert isinstance(bus, canlib.VectorBus)
bus.shutdown()
@@ -112,7 +112,7 @@ def test_bus_creation() -> None:
def test_bus_creation_bitrate_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", bitrate=200_000, _testing=True)
+ bus = can.Bus(channel=0, interface="vector", bitrate=200_000, _testing=True)
assert isinstance(bus, canlib.VectorBus)
can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
@@ -133,7 +133,10 @@ def test_bus_creation_bitrate_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_bus_creation_bitrate() -> None:
bus = can.Bus(
- channel=0, serial=_find_virtual_can_serial(), bustype="vector", bitrate=200_000
+ channel=0,
+ serial=_find_virtual_can_serial(),
+ interface="vector",
+ bitrate=200_000,
)
assert isinstance(bus, canlib.VectorBus)
@@ -146,7 +149,7 @@ def test_bus_creation_bitrate() -> None:
def test_bus_creation_fd_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True)
assert isinstance(bus, canlib.VectorBus)
can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
@@ -165,7 +168,7 @@ def test_bus_creation_fd_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_bus_creation_fd() -> None:
bus = can.Bus(
- channel=0, serial=_find_virtual_can_serial(), bustype="vector", fd=True
+ channel=0, serial=_find_virtual_can_serial(), interface="vector", fd=True
)
assert isinstance(bus, canlib.VectorBus)
@@ -186,7 +189,7 @@ def test_bus_creation_fd() -> None:
def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None:
bus = can.Bus(
channel=0,
- bustype="vector",
+ interface="vector",
fd=True,
bitrate=500_000,
data_bitrate=2_000_000,
@@ -232,7 +235,7 @@ def test_bus_creation_fd_bitrate_timings() -> None:
bus = can.Bus(
channel=0,
serial=_find_virtual_can_serial(),
- bustype="vector",
+ interface="vector",
fd=True,
bitrate=500_000,
data_bitrate=2_000_000,
@@ -268,7 +271,7 @@ def test_bus_creation_fd_bitrate_timings() -> None:
def test_send_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -278,7 +281,7 @@ def test_send_mocked(mock_xldriver) -> None:
def test_send_fd_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True)
msg = can.Message(
arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True
)
@@ -289,7 +292,7 @@ def test_send_fd_mocked(mock_xldriver) -> None:
def test_receive_mocked(mock_xldriver) -> None:
can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive)
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
bus.recv(timeout=0.05)
can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called()
@@ -297,7 +300,7 @@ def test_receive_mocked(mock_xldriver) -> None:
def test_receive_fd_mocked(mock_xldriver) -> None:
can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(side_effect=xlCanReceive)
- bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True)
bus.recv(timeout=0.05)
can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called()
@@ -305,8 +308,8 @@ def test_receive_fd_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_send_and_receive() -> None:
- bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
- bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector")
+ bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector")
msg_std = can.Message(
channel=0, arbitration_id=0xFF, data=list(range(8)), is_extended_id=False
@@ -330,10 +333,10 @@ def test_send_and_receive() -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_send_and_receive_fd() -> None:
bus1 = can.Bus(
- channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector"
)
bus2 = can.Bus(
- channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector"
)
msg_std = can.Message(
@@ -367,7 +370,7 @@ def test_receive_non_msg_event_mocked(mock_xldriver) -> None:
can.interfaces.vector.canlib.xldriver.xlReceive = Mock(
side_effect=xlReceive_chipstate
)
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
bus.handle_can_event = Mock()
bus.recv(timeout=0.05)
can.interfaces.vector.canlib.xldriver.xlReceive.assert_called()
@@ -378,7 +381,7 @@ def test_receive_non_msg_event_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_receive_non_msg_event() -> None:
bus = canlib.VectorBus(
- channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), interface="vector"
)
bus.handle_can_event = Mock()
bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0])
@@ -391,7 +394,7 @@ def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None:
can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(
side_effect=xlCanReceive_chipstate
)
- bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True)
bus.handle_canfd_event = Mock()
bus.recv(timeout=0.05)
can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called()
@@ -402,7 +405,7 @@ def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_receive_fd_non_msg_event() -> None:
bus = canlib.VectorBus(
- channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector"
)
bus.handle_canfd_event = Mock()
bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0])
@@ -412,20 +415,20 @@ def test_receive_fd_non_msg_event() -> None:
def test_flush_tx_buffer_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
bus.flush_tx_buffer()
can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called()
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_flush_tx_buffer() -> None:
- bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector")
bus.flush_tx_buffer()
bus.shutdown()
def test_shutdown_mocked(mock_xldriver) -> None:
- bus = can.Bus(channel=0, bustype="vector", _testing=True)
+ bus = can.Bus(channel=0, interface="vector", _testing=True)
bus.shutdown()
can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called()
@@ -434,7 +437,7 @@ def test_shutdown_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_shutdown() -> None:
- bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector")
+ bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector")
xl_channel_config = _find_xl_channel_config(
serial=_find_virtual_can_serial(), channel=0
@@ -449,7 +452,7 @@ def test_shutdown() -> None:
def test_reset_mocked(mock_xldriver) -> None:
- bus = canlib.VectorBus(channel=0, bustype="vector", _testing=True)
+ bus = canlib.VectorBus(channel=0, interface="vector", _testing=True)
bus.reset()
can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called()
can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called()
@@ -458,7 +461,7 @@ def test_reset_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_reset_mocked() -> None:
bus = canlib.VectorBus(
- channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), interface="vector"
)
bus.reset()
bus.shutdown()
@@ -520,7 +523,7 @@ def test_set_and_get_application_config() -> None:
def test_set_timer_mocked(mock_xldriver) -> None:
canlib.xldriver.xlSetTimerRate = Mock()
- bus = canlib.VectorBus(channel=0, bustype="vector", fd=True, _testing=True)
+ bus = canlib.VectorBus(channel=0, interface="vector", fd=True, _testing=True)
bus.set_timer_rate(timer_rate_ms=1)
assert canlib.xldriver.xlSetTimerRate.called
@@ -528,7 +531,7 @@ def test_set_timer_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_set_timer() -> None:
bus = canlib.VectorBus(
- channel=0, serial=_find_virtual_can_serial(), bustype="vector"
+ channel=0, serial=_find_virtual_can_serial(), interface="vector"
)
bus.handle_can_event = Mock()
bus.set_timer_rate(timer_rate_ms=1)
@@ -546,7 +549,7 @@ def test_called_without_testing_argument() -> None:
"""This tests if an exception is thrown when we are not running on Windows."""
with pytest.raises(can.CanInterfaceNotImplementedError):
# do not set the _testing argument, since it would suppress the exception
- can.Bus(channel=0, bustype="vector")
+ can.Bus(channel=0, interface="vector")
def test_vector_error_pickle() -> None:
diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py
index dd7c0dd49..cd5e7895e 100644
--- a/test/zero_dlc_test.py
+++ b/test/zero_dlc_test.py
@@ -14,8 +14,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")
+ bus_send = can.interface.Bus(interface="virtual")
+ bus_recv = can.interface.Bus(interface="virtual")
data = [0, 1, 2, 3, 4, 5, 6, 7]
msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data)
@@ -26,7 +26,7 @@ def test_recv_non_zero_dlc(self):
self.assertTrue(msg_recv)
def test_recv_none(self):
- bus_recv = can.interface.Bus(bustype="virtual")
+ bus_recv = can.interface.Bus(interface="virtual")
msg_recv = bus_recv.recv(timeout=0)
@@ -34,8 +34,8 @@ def test_recv_none(self):
self.assertFalse(msg_recv)
def test_recv_zero_dlc(self):
- bus_send = can.interface.Bus(bustype="virtual")
- bus_recv = can.interface.Bus(bustype="virtual")
+ bus_send = can.interface.Bus(interface="virtual")
+ bus_recv = can.interface.Bus(interface="virtual")
msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[])
bus_send.send(msg_send)
From 11f983aae4b4975bcdbc1a85ad077adf12f68b63 Mon Sep 17 00:00:00 2001
From: felixdivo
Date: Wed, 21 Dec 2022 17:00:18 +0000
Subject: [PATCH 201/475] Format code with black
---
can/interfaces/__init__.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index f9aee6112..626a85f8b 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -42,11 +42,17 @@
# The second variant causes a deprecation warning on Python >= 3.10.
if sys.version_info >= (3, 10):
BACKENDS.update(
- {interface.name: (interface.module, interface.attr) for interface in entry_points(group="can.interface")}
+ {
+ interface.name: (interface.module, interface.attr)
+ for interface in entry_points(group="can.interface")
+ }
)
else:
BACKENDS.update(
- {interface.name: tuple(interface.value.split(":")) for interface in entry_points().get("can.interface", [])}
+ {
+ interface.name: tuple(interface.value.split(":"))
+ for interface in entry_points().get("can.interface", [])
+ }
)
else:
from pkg_resources import iter_entry_points
From 5a202c9d1d87273f6535a347dd683daa49aa8b22 Mon Sep 17 00:00:00 2001
From: Felix Divo
Date: Wed, 21 Dec 2022 18:27:39 +0100
Subject: [PATCH 202/475] Finally fix typing
---
can/interfaces/__init__.py | 58 ++++++++++++++++++--------------------
1 file changed, 28 insertions(+), 30 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 626a85f8b..24d592abb 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -6,37 +6,34 @@
from typing import cast, Dict, Iterable, Tuple
# interface_name => (module, classname)
-BACKENDS: Dict[str, Tuple[str, str]] = cast(
- Dict[str, Tuple[str, str]],
- {
- "kvaser": ("can.interfaces.kvaser", "KvaserBus"),
- "socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
- "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"),
- "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"),
- "neovi": ("can.interfaces.ics_neovi", "NeoViBus"),
- "vector": ("can.interfaces.vector", "VectorBus"),
- "slcan": ("can.interfaces.slcan", "slcanBus"),
- "robotell": ("can.interfaces.robotell", "robotellBus"),
- "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
- "systec": ("can.interfaces.systec", "UcanBus"),
- "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
- "cantact": ("can.interfaces.cantact", "CantactBus"),
- "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
- "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
- "neousys": ("can.interfaces.neousys", "NeousysBus"),
- "etas": ("can.interfaces.etas", "EtasBus"),
- "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
- },
-)
+BACKENDS: Dict[str, Tuple[str, str]] = {
+ "kvaser": ("can.interfaces.kvaser", "KvaserBus"),
+ "socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
+ "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"),
+ "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"),
+ "neovi": ("can.interfaces.ics_neovi", "NeoViBus"),
+ "vector": ("can.interfaces.vector", "VectorBus"),
+ "slcan": ("can.interfaces.slcan", "slcanBus"),
+ "robotell": ("can.interfaces.robotell", "robotellBus"),
+ "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
+ "systec": ("can.interfaces.systec", "UcanBus"),
+ "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
+ "cantact": ("can.interfaces.cantact", "CantactBus"),
+ "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
+ "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
+ "neousys": ("can.interfaces.neousys", "NeousysBus"),
+ "etas": ("can.interfaces.etas", "EtasBus"),
+ "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
+}
if sys.version_info >= (3, 8):
- from importlib.metadata import entry_points, EntryPoint
+ from importlib.metadata import entry_points
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
# The second variant causes a deprecation warning on Python >= 3.10.
@@ -50,7 +47,8 @@
else:
BACKENDS.update(
{
- interface.name: tuple(interface.value.split(":"))
+ # This cast in wrong if interface.value is formatted badly, but we just fail later
+ interface.name: cast(Tuple[str, str], tuple(interface.value.split(":")))
for interface in entry_points().get("can.interface", [])
}
)
From 18b86bab92dd03e1f8d775e9cd5cacdad5fe2be5 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Wed, 21 Dec 2022 18:34:28 +0100
Subject: [PATCH 203/475] Update can/interfaces/__init__.py
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/interfaces/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 24d592abb..8754666a0 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -48,7 +48,7 @@
BACKENDS.update(
{
# This cast in wrong if interface.value is formatted badly, but we just fail later
- interface.name: cast(Tuple[str, str], tuple(interface.value.split(":")))
+ interface.name: cast(Tuple[str, str], tuple(interface.value.split(":", maxsplit=1)))
for interface in entry_points().get("can.interface", [])
}
)
From 057b67770b8b786aff886ee143f3946b6fbc092f Mon Sep 17 00:00:00 2001
From: felixdivo
Date: Wed, 21 Dec 2022 17:35:06 +0000
Subject: [PATCH 204/475] Format code with black
---
can/interfaces/__init__.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 8754666a0..5936819e0 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -48,7 +48,9 @@
BACKENDS.update(
{
# This cast in wrong if interface.value is formatted badly, but we just fail later
- interface.name: cast(Tuple[str, str], tuple(interface.value.split(":", maxsplit=1)))
+ interface.name: cast(
+ Tuple[str, str], tuple(interface.value.split(":", maxsplit=1))
+ )
for interface in entry_points().get("can.interface", [])
}
)
From e968bcbb506555349839c4e1ed299aa0211450b2 Mon Sep 17 00:00:00 2001
From: Felix Divo
Date: Wed, 21 Dec 2022 18:41:09 +0100
Subject: [PATCH 205/475] Cleanup
---
can/interfaces/__init__.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 5936819e0..8c7d016bc 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -3,7 +3,7 @@
"""
import sys
-from typing import cast, Dict, Iterable, Tuple
+from typing import cast, Dict, Tuple
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, str]] = {
@@ -36,7 +36,6 @@
from importlib.metadata import entry_points
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
- # The second variant causes a deprecation warning on Python >= 3.10.
if sys.version_info >= (3, 10):
BACKENDS.update(
{
@@ -45,6 +44,7 @@
}
)
else:
+ # The entry_points().get(...) causes a deprecation warning on Python >= 3.10.
BACKENDS.update(
{
# This cast in wrong if interface.value is formatted badly, but we just fail later
@@ -57,11 +57,10 @@
else:
from pkg_resources import iter_entry_points
- entries = iter_entry_points("can.interface")
BACKENDS.update(
{
interface.name: (interface.module_name, interface.attrs[0])
- for interface in entries
+ for interface in iter_entry_points("can.interface")
}
)
From e50490c8029bda301bdbf54d28c03ed95968a5a2 Mon Sep 17 00:00:00 2001
From: Felix Divo
Date: Wed, 21 Dec 2022 18:45:14 +0100
Subject: [PATCH 206/475] Cleanup
---
can/interfaces/__init__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 8c7d016bc..3065e9bfd 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -47,7 +47,6 @@
# The entry_points().get(...) causes a deprecation warning on Python >= 3.10.
BACKENDS.update(
{
- # This cast in wrong if interface.value is formatted badly, but we just fail later
interface.name: cast(
Tuple[str, str], tuple(interface.value.split(":", maxsplit=1))
)
From e9252de77f8dceccf9d8249ff86088396ace8d11 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Schwaiger?=
Date: Wed, 21 Dec 2022 19:22:58 +0100
Subject: [PATCH 207/475] PCAN: Fix detection of lib on Windows on ARM (#1463)
Before this commit detection of the PCAN DLL would fail on Windows on
ARM, regardless if you used the native Python version, or the x64
version of Python. After this fix detection should work properly as long
as the PCAN library for your version of Python is listed first in the
`PATH` variable. The default:
1. `C:\Program Files\PEAK-System\PEAK-Drivers 4\APIs\ARM64\` before
2. `C:\Program Files\PEAK-System\PEAK-Drivers 4\APIs\x64\`
should work if you use the (native) ARM version of Python. If you
reorder these paths, then loading the library works in the x64 version
of Python.
This commit closes #1461.
---
can/interfaces/pcan/basic.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index 743fb55ee..171fae96d 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -658,7 +658,7 @@ def __init__(self):
#
if platform.system() == "Windows":
# Loads the API on Windows
- self.__m_dllBasic = windll.LoadLibrary("PCANBasic")
+ self.__m_dllBasic = windll.LoadLibrary(find_library("PCANBasic"))
aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers")
From b77527865bc2ca1da415111d5c4e7d6ae6bf5a5b Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 21 Dec 2022 19:50:03 +0100
Subject: [PATCH 208/475] fix TypeError (#1466)
---
can/interfaces/pcan/basic.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index 171fae96d..e9fc5029a 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -658,7 +658,8 @@ def __init__(self):
#
if platform.system() == "Windows":
# Loads the API on Windows
- self.__m_dllBasic = windll.LoadLibrary(find_library("PCANBasic"))
+ _dll_path = find_library("PCANBasic")
+ self.__m_dllBasic = windll.LoadLibrary(_dll_path) if _dll_path else None
aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers")
From 3f314dc6582572446cbc33458a53c8eb47860d0b Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 21 Dec 2022 21:47:01 +0100
Subject: [PATCH 209/475] Add conda badge and fix GHA badge (#1467)
---
README.rst | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/README.rst b/README.rst
index ba32607d4..6e65e505c 100644
--- a/README.rst
+++ b/README.rst
@@ -1,14 +1,18 @@
python-can
==========
-|release| |python_implementation| |downloads| |downloads_monthly| |formatter|
+|pypi| |conda| |python_implementation| |downloads| |downloads_monthly|
-|docs| |github-actions| |build_travis| |coverage| |mergify|
+|docs| |github-actions| |build_travis| |coverage| |mergify| |formatter|
-.. |release| image:: https://img.shields.io/pypi/v/python-can.svg
+.. |pypi| image:: https://img.shields.io/pypi/v/python-can.svg
:target: https://pypi.python.org/pypi/python-can/
:alt: Latest Version on PyPi
+.. |conda| image:: https://img.shields.io/conda/v/conda-forge/python-can
+ :target: https://github.com/conda-forge/python-can-feedstock
+ :alt: Latest Version on conda-forge
+
.. |python_implementation| image:: https://img.shields.io/pypi/implementation/python-can
:target: https://pypi.python.org/pypi/python-can/
:alt: Supported Python implementations
@@ -29,8 +33,8 @@ python-can
:target: https://python-can.readthedocs.io/en/stable/
:alt: Documentation
-.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/build.yml/badge.svg?branch=develop
- :target: https://github.com/hardbyte/python-can/actions/workflows/build.yml
+.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/ci.yml/badge.svg
+ :target: https://github.com/hardbyte/python-can/actions/workflows/ci.yml
:alt: Github Actions workflow status
.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI
From ddeef2e9672065673e55805faf1d4c3bfabb344d Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 21 Dec 2022 22:44:26 +0100
Subject: [PATCH 210/475] fix indentation
---
doc/other-tools.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/other-tools.rst b/doc/other-tools.rst
index 56ec21083..eab3c4f43 100644
--- a/doc/other-tools.rst
+++ b/doc/other-tools.rst
@@ -28,8 +28,8 @@ CAN Message protocols (implemented in Python)
for performing UDS over CAN utilising the ISO TP protocol. This module has not been updated
for some time.
* The `uds`_ module is another tool that implements the UDS protocol, although it does have
- extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not
- been updated for some time.
+ extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not
+ been updated for some time.
#. XCP
* The `pyxcp`_ module implements the Universal Measurement and Calibration Protocol (XCP).
The purpose of XCP is to adjust parameters and acquire current values of internal
From 9977c71b5ec143d0883170fc7df2201ae03b4843 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 28 Dec 2022 06:40:20 +0100
Subject: [PATCH 211/475] Turn sphinx warnings into errors (#1472)
* fix nixnet sphinx warnings
* treat sphinx warnings as errors
* fix mypy
---
.github/workflows/ci.yml | 2 +-
can/interfaces/nixnet.py | 90 ++++++++++++++++++++-------------------
doc/interfaces/nixnet.rst | 7 +--
3 files changed, 52 insertions(+), 47 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 577ccd97d..8bcc273ab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -127,7 +127,7 @@ jobs:
pip install -r doc/doc-requirements.txt
- name: Build documentation
run: |
- python -m sphinx -an doc build
+ python -m sphinx -Wan doc build
- uses: actions/upload-artifact@v3
with:
name: sphinx-out
diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py
index 1eba09b31..e304fbc1f 100644
--- a/can/interfaces/nixnet.py
+++ b/can/interfaces/nixnet.py
@@ -9,31 +9,24 @@
"""
import logging
-import sys
-import time
-import struct
+import os
+from types import ModuleType
+from typing import Optional
from can import BusABC, Message
-from ..exceptions import CanInitializationError, CanOperationError
-
+from ..exceptions import (
+ CanInitializationError,
+ CanOperationError,
+ CanInterfaceNotImplementedError,
+)
logger = logging.getLogger(__name__)
-if sys.platform == "win32":
- try:
- from nixnet import (
- session,
- types,
- constants,
- errors,
- system,
- database,
- XnetError,
- )
- except ImportError as error:
- raise ImportError("NIXNET python module cannot be loaded") from error
-else:
- raise NotImplementedError("NiXNET only supported on Win32 platforms")
+nixnet: Optional[ModuleType] = None
+try:
+ import nixnet # type: ignore
+except Exception as exc:
+ logger.warning("Could not import nixnet: %s", exc)
class NiXNETcanBus(BusABC):
@@ -68,9 +61,18 @@ def __init__(
``is_error_frame`` set to True and ``arbitration_id`` will identify
the error (default True)
- :raises can.exceptions.CanInitializationError:
+ :raises ~can.exceptions.CanInitializationError:
If starting communication fails
"""
+ if os.name != "nt" and not kwargs.get("_testing", False):
+ raise CanInterfaceNotImplementedError(
+ f"The NI-XNET interface is only supported on Windows, "
+ f'but you are running "{os.name}"'
+ )
+
+ if nixnet is None:
+ raise CanInterfaceNotImplementedError("The NI-XNET API has not been loaded")
+
self._rx_queue = []
self.channel = channel
self.channel_info = "NI-XNET: " + channel
@@ -88,10 +90,10 @@ def __init__(
# We need two sessions for this application, one to send frames and another to receive them
- self.__session_send = session.FrameOutStreamSession(
+ self.__session_send = nixnet.session.FrameOutStreamSession(
channel, database_name=database_name
)
- self.__session_receive = session.FrameInStreamSession(
+ self.__session_receive = nixnet.session.FrameInStreamSession(
channel, database_name=database_name
)
@@ -110,15 +112,15 @@ def __init__(
self.__session_receive.intf.can_fd_baud_rate = fd_bitrate
if can_termination:
- self.__session_send.intf.can_term = constants.CanTerm.ON
- self.__session_receive.intf.can_term = constants.CanTerm.ON
+ self.__session_send.intf.can_term = nixnet.constants.CanTerm.ON
+ self.__session_receive.intf.can_term = nixnet.constants.CanTerm.ON
self.__session_receive.queue_size = 512
# Once that all the parameters have been restarted, we start the sessions
self.__session_send.start()
self.__session_receive.start()
- except errors.XnetError as error:
+ except nixnet.errors.XnetError as error:
raise CanInitializationError(
f"{error.args[0]} ({error.error_type})", error.error_code
) from None
@@ -145,13 +147,15 @@ def _recv_internal(self, timeout):
msg = Message(
timestamp=can_frame.timestamp / 10000000.0 - 11644473600,
channel=self.channel,
- is_remote_frame=can_frame.type == constants.FrameType.CAN_REMOTE,
- is_error_frame=can_frame.type == constants.FrameType.CAN_BUS_ERROR,
+ is_remote_frame=can_frame.type == nixnet.constants.FrameType.CAN_REMOTE,
+ is_error_frame=can_frame.type
+ == nixnet.constants.FrameType.CAN_BUS_ERROR,
is_fd=(
- can_frame.type == constants.FrameType.CANFD_DATA
- or can_frame.type == constants.FrameType.CANFDBRS_DATA
+ can_frame.type == nixnet.constants.FrameType.CANFD_DATA
+ or can_frame.type == nixnet.constants.FrameType.CANFDBRS_DATA
),
- bitrate_switch=can_frame.type == constants.FrameType.CANFDBRS_DATA,
+ bitrate_switch=can_frame.type
+ == nixnet.constants.FrameType.CANFDBRS_DATA,
is_extended_id=can_frame.identifier.extended,
# Get identifier from CanIdentifier structure
arbitration_id=can_frame.identifier.identifier,
@@ -178,29 +182,29 @@ def send(self, msg, timeout=None):
It does not wait for message to be ACKed currently.
"""
if timeout is None:
- timeout = constants.TIMEOUT_INFINITE
+ timeout = nixnet.constants.TIMEOUT_INFINITE
if msg.is_remote_frame:
- type_message = constants.FrameType.CAN_REMOTE
+ type_message = nixnet.constants.FrameType.CAN_REMOTE
elif msg.is_error_frame:
- type_message = constants.FrameType.CAN_BUS_ERROR
+ type_message = nixnet.constants.FrameType.CAN_BUS_ERROR
elif msg.is_fd:
if msg.bitrate_switch:
- type_message = constants.FrameType.CANFDBRS_DATA
+ type_message = nixnet.constants.FrameType.CANFDBRS_DATA
else:
- type_message = constants.FrameType.CANFD_DATA
+ type_message = nixnet.constants.FrameType.CANFD_DATA
else:
- type_message = constants.FrameType.CAN_DATA
+ type_message = nixnet.constants.FrameType.CAN_DATA
- can_frame = types.CanFrame(
- types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
+ can_frame = nixnet.types.CanFrame(
+ nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
type=type_message,
payload=msg.data,
)
try:
self.__session_send.frames.write([can_frame], timeout)
- except errors.XnetError as error:
+ except nixnet.errors.XnetError as error:
raise CanOperationError(
f"{error.args[0]} ({error.error_type})", error.error_code
) from None
@@ -237,7 +241,7 @@ def _detect_available_configs():
configs = []
try:
- with system.System() as nixnet_system:
+ with nixnet.system.System() as nixnet_system:
for interface in nixnet_system.intf_refs_can:
cahnnel = str(interface)
logger.debug(
@@ -248,10 +252,10 @@ def _detect_available_configs():
"interface": "nixnet",
"channel": cahnnel,
"can_term_available": interface.can_term_cap
- == constants.CanTermCap.YES,
+ == nixnet.constants.CanTermCap.YES,
}
)
- except XnetError as error:
+ except Exception as error:
logger.debug("An error occured while searching for configs: %s", str(error))
return configs
diff --git a/doc/interfaces/nixnet.rst b/doc/interfaces/nixnet.rst
index 8cf2ee72d..5a17e7e8d 100644
--- a/doc/interfaces/nixnet.rst
+++ b/doc/interfaces/nixnet.rst
@@ -12,9 +12,10 @@ This interface adds support for NI-XNET CAN controllers by `National Instruments
Bus
---
-.. autoclass:: can.interfaces.nican.NiXNETcanBus
-
-.. autoexception:: can.interfaces.nican.NiXNETError
+.. autoclass:: can.interfaces.nixnet.NiXNETcanBus
+ :show-inheritance:
+ :member-order: bysource
+ :members:
.. _National Instruments: http://www.ni.com/can/
From eb0331d140afd9ee940d57a5a5f324e6da9fbb15 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 31 Dec 2022 19:42:57 +0100
Subject: [PATCH 212/475] Update pylint (#1471)
* update pylint version
* add coverage.lcov to .gitignore
* check can/io with pylint
* check can/interfaces/socketcan with pylint
* address review comments
* replace wildcard import
* fix TRCWriter line endings
Co-authored-by: zariiii9003
---
.github/workflows/ci.yml | 6 +-
.gitignore | 1 +
.pylintrc | 3 +-
can/bus.py | 4 +-
can/interfaces/socketcan/socketcan.py | 94 +++++++++--------
can/interfaces/socketcan/utils.py | 3 +-
can/io/asc.py | 13 +--
can/io/blf.py | 2 -
can/io/canutils.py | 10 +-
can/io/csv.py | 2 -
can/io/generic.py | 25 +++--
can/io/logger.py | 28 +++--
can/io/player.py | 5 +-
can/io/printer.py | 1 -
can/io/sqlite.py | 2 -
can/io/trc.py | 142 ++++++++++++--------------
can/listener.py | 10 +-
can/logconvert.py | 2 +-
can/logger.py | 2 +-
can/message.py | 4 +-
requirements-lint.txt | 2 +-
21 files changed, 170 insertions(+), 191 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8bcc273ab..c6a5c4253 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -91,10 +91,12 @@ jobs:
run: |
pylint --rcfile=.pylintrc \
can/**.py \
+ can/io \
setup.py \
- doc.conf \
+ doc/conf.py \
scripts/**.py \
- examples/**.py
+ examples/**.py \
+ can/interfaces/socketcan
format:
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index 258ca73ea..03775bd7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
+coverage.lcov
*,cover
.hypothesis/
test.*
diff --git a/.pylintrc b/.pylintrc
index a42935e6a..cc4c50d88 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -81,7 +81,8 @@ disable=invalid-name,
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
-enable=c-extension-no-member
+enable=c-extension-no-member,
+ useless-suppression,
[REPORTS]
diff --git a/can/bus.py b/can/bus.py
index c0793c5f7..d0192fc2d 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -464,7 +464,5 @@ class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC):
Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate.
"""
- def stop( # pylint: disable=arguments-differ
- self, remove_task: bool = True
- ) -> None:
+ def stop(self, remove_task: bool = True) -> None:
raise NotImplementedError()
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index f0545f7df..74fbe8197 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -21,12 +21,6 @@
log_tx = log.getChild("tx")
log_rx = log.getChild("rx")
-try:
- import fcntl
-except ImportError:
- log.error("fcntl not available on this platform")
-
-
try:
from socket import CMSG_SPACE
@@ -44,7 +38,7 @@
LimitedDurationCyclicSendTaskABC,
)
from can.typechecking import CanFilters
-from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG
+from can.interfaces.socketcan import constants
from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces
@@ -177,9 +171,9 @@ def build_can_frame(msg: Message) -> bytes:
can_id = _compose_arbitration_id(msg)
flags = 0
if msg.bitrate_switch:
- flags |= CANFD_BRS
+ flags |= constants.CANFD_BRS
if msg.error_state_indicator:
- flags |= CANFD_ESI
+ flags |= constants.CANFD_ESI
max_len = 64 if msg.is_fd else 8
data = bytes(msg.data).ljust(max_len, b"\x00")
return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data
@@ -211,7 +205,7 @@ def build_bcm_header(
def build_bcm_tx_delete_header(can_id: int, flags: int) -> bytes:
- opcode = CAN_BCM_TX_DELETE
+ opcode = constants.CAN_BCM_TX_DELETE
return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1)
@@ -223,13 +217,13 @@ def build_bcm_transmit_header(
msg_flags: int,
nframes: int = 1,
) -> bytes:
- opcode = CAN_BCM_TX_SETUP
+ opcode = constants.CAN_BCM_TX_SETUP
- flags = msg_flags | SETTIMER | STARTTIMER
+ flags = msg_flags | constants.SETTIMER | constants.STARTTIMER
if initial_period > 0:
# Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires
- flags |= TX_COUNTEVT
+ flags |= constants.TX_COUNTEVT
def split_time(value: float) -> Tuple[int, int]:
"""Given seconds as a float, return whole seconds and microseconds"""
@@ -254,12 +248,14 @@ def split_time(value: float) -> Tuple[int, int]:
def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> bytes:
- return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes)
+ return build_bcm_header(
+ constants.CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes
+ )
def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]:
can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame)
- if len(frame) != CANFD_MTU:
+ if len(frame) != constants.CANFD_MTU:
# Flags not valid in non-FD frames
flags = 0
return can_id, can_dlc, flags, frame[8 : 8 + can_dlc]
@@ -267,7 +263,7 @@ def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]:
def create_bcm_socket(channel: str) -> socket.socket:
"""create a broadcast manager socket and connect to the given interface"""
- s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM)
+ s = socket.socket(constants.PF_CAN, socket.SOCK_DGRAM, constants.CAN_BCM)
s.connect((channel,))
return s
@@ -297,13 +293,13 @@ def _compose_arbitration_id(message: Message) -> int:
can_id = message.arbitration_id
if message.is_extended_id:
log.debug("sending an extended id type message")
- can_id |= CAN_EFF_FLAG
+ can_id |= constants.CAN_EFF_FLAG
if message.is_remote_frame:
log.debug("requesting a remote frame")
- can_id |= CAN_RTR_FLAG
+ can_id |= constants.CAN_RTR_FLAG
if message.is_error_frame:
log.debug("sending error frame")
- can_id |= CAN_ERR_FLAG
+ can_id |= constants.CAN_ERR_FLAG
return can_id
@@ -354,7 +350,7 @@ def _tx_setup(
) -> None:
# Create a low level packed frame to pass to the kernel
body = bytearray()
- self.flags = CAN_FD_FRAME if messages[0].is_fd else 0
+ self.flags = constants.CAN_FD_FRAME if messages[0].is_fd else 0
if self.duration:
count = int(self.duration / self.period)
@@ -380,7 +376,7 @@ def _check_bcm_task(self) -> None:
# Do a TX_READ on a task ID, and check if we get EINVAL. If so,
# then we are referring to a CAN message with an existing ID
check_header = build_bcm_header(
- opcode=CAN_BCM_TX_READ,
+ opcode=constants.CAN_BCM_TX_READ,
flags=0,
count=0,
ival1_seconds=0,
@@ -391,7 +387,7 @@ def _check_bcm_task(self) -> None:
nframes=0,
)
log.debug(
- f"Reading properties of (cyclic) transmission task id={self.task_id}",
+ "Reading properties of (cyclic) transmission task id=%d", self.task_id
)
try:
self.bcm_socket.send(check_header)
@@ -495,7 +491,7 @@ def create_socket() -> socket.socket:
"""Creates a raw CAN socket. The socket will
be returned unbound to any interface.
"""
- sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW)
+ sock = socket.socket(constants.PF_CAN, socket.SOCK_RAW, constants.CAN_RAW)
log.info("Created a socket")
@@ -534,7 +530,7 @@ def capture_message(
# Fetching the Arb ID, DLC and Data
try:
cf, ancillary_data, msg_flags, addr = sock.recvmsg(
- CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE
+ constants.CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE
)
if get_channel:
channel = addr[0] if isinstance(addr, tuple) else addr
@@ -549,7 +545,7 @@ def capture_message(
assert len(ancillary_data) == 1, "only requested a single extra field"
cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
assert (
- cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS
+ cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS
), "received control message type that was not requested"
# see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data)
@@ -564,12 +560,12 @@ def capture_message(
# #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 & 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)
+ is_extended_frame_format = bool(can_id & constants.CAN_EFF_FLAG)
+ is_remote_transmission_request = bool(can_id & constants.CAN_RTR_FLAG)
+ is_error_frame = bool(can_id & constants.CAN_ERR_FLAG)
+ is_fd = len(cf) == constants.CANFD_MTU
+ bitrate_switch = bool(flags & constants.CANFD_BRS)
+ error_state_indicator = bool(flags & constants.CANFD_ESI)
# Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host.
is_rx = not bool(msg_flags & socket.MSG_DONTROUTE)
@@ -625,8 +621,8 @@ def __init__(
) -> None:
"""Creates a new socketcan bus.
- If setting some socket options fails, an error will be printed but no exception will be thrown.
- This includes enabling:
+ If setting some socket options fails, an error will be printed
+ but no exception will be thrown. This includes enabling:
- that own messages should be received,
- CAN-FD frames and
@@ -656,7 +652,7 @@ def __init__(
"""
self.socket = create_socket()
self.channel = channel
- self.channel_info = "socketcan channel '%s'" % channel
+ self.channel_info = f"socketcan channel '{channel}'"
self._bcm_sockets: Dict[str, socket.socket] = {}
self._is_filtered = False
self._task_id = 0
@@ -665,7 +661,9 @@ def __init__(
# set the local_loopback parameter
try:
self.socket.setsockopt(
- SOL_CAN_RAW, CAN_RAW_LOOPBACK, 1 if local_loopback else 0
+ constants.SOL_CAN_RAW,
+ constants.CAN_RAW_LOOPBACK,
+ 1 if local_loopback else 0,
)
except OSError as error:
log.error("Could not set local loopback flag(%s)", error)
@@ -673,7 +671,9 @@ def __init__(
# set the receive_own_messages parameter
try:
self.socket.setsockopt(
- SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0
+ constants.SOL_CAN_RAW,
+ constants.CAN_RAW_RECV_OWN_MSGS,
+ 1 if receive_own_messages else 0,
)
except OSError as error:
log.error("Could not receive own messages (%s)", error)
@@ -681,23 +681,27 @@ def __init__(
# enable CAN-FD frames if desired
if fd:
try:
- self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1)
+ self.socket.setsockopt(
+ constants.SOL_CAN_RAW, constants.CAN_RAW_FD_FRAMES, 1
+ )
except OSError as error:
log.error("Could not enable CAN-FD frames (%s)", error)
if not ignore_rx_error_frames:
# enable error frames
try:
- self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF)
+ self.socket.setsockopt(
+ constants.SOL_CAN_RAW, constants.CAN_RAW_ERR_FILTER, 0x1FFFFFFF
+ )
except OSError as error:
log.error("Could not enable error frames (%s)", error)
# enable nanosecond resolution timestamping
# we can always do this since
- # 1) is is guaranteed to be at least as precise as without
+ # 1) it is guaranteed to be at least as precise as without
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
# so this is always supported by the kernel
- self.socket.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
+ self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
bind_socket(self.socket, channel)
kwargs.update(
@@ -830,7 +834,9 @@ def _send_periodic_internal(
general the message will be sent at the given rate until at
least *duration* seconds.
"""
- msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs)
+ msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access
+ msgs
+ )
msgs_channel = str(msgs[0].channel) if msgs[0].channel else None
bcm_socket = self._get_bcm_socket(msgs_channel or self.channel)
@@ -850,7 +856,9 @@ def _get_bcm_socket(self, channel: str) -> socket.socket:
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
try:
- self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters))
+ self.socket.setsockopt(
+ constants.SOL_CAN_RAW, constants.CAN_RAW_FILTER, pack_filters(filters)
+ )
except OSError as error:
# fall back to "software filtering" (= not in kernel)
self._is_filtered = False
@@ -899,8 +907,6 @@ def sender(event: threading.Event) -> None:
sender_socket.send(build_can_frame(msg))
print("Sender sent a message.")
- import threading
-
e = threading.Event()
threading.Thread(target=receiver, args=(e,)).start()
threading.Thread(target=sender, args=(e,)).start()
diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py
index ecc870ca4..25f04617f 100644
--- a/can/interfaces/socketcan/utils.py
+++ b/can/interfaces/socketcan/utils.py
@@ -52,7 +52,8 @@ def find_available_interfaces() -> Iterable[str]:
command = ["ip", "-o", "link", "list", "up"]
output = subprocess.check_output(command, text=True)
- except Exception as e: # subprocess.CalledProcessError is too specific
+ except Exception as e: # pylint: disable=broad-except
+ # subprocess.CalledProcessError is too specific
log.error("failed to fetch opened can devices: %s", e)
return []
diff --git a/can/io/asc.py b/can/io/asc.py
index 7cefa5b76..eb59c0471 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -2,7 +2,7 @@
Contains handling of ASC logging files.
Example .asc files:
- - 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
+ - https://bitbucket.org/tobylorenz/vector_asc/src/master/src/Vector/ASC/tests/unittests/data/
- under `test/data/logfile.asc`
"""
import re
@@ -39,7 +39,6 @@ def __init__(
file: Union[StringPathLike, TextIO],
base: str = "hex",
relative_timestamp: bool = True,
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -93,7 +92,7 @@ def _extract_header(self) -> None:
)
continue
- elif base_match:
+ if base_match:
base = base_match.group("base")
timestamp_format = base_match.group("timestamp_format")
self.base = base
@@ -101,15 +100,14 @@ def _extract_header(self) -> None:
self.timestamps_format = timestamp_format or "absolute"
continue
- elif comment_match:
+ if comment_match:
continue
- elif events_match:
+ if events_match:
self.internal_events_logged = events_match.group("no_events") is None
break
- else:
- break
+ break
@staticmethod
def _datetime_to_timestamp(datetime_string: str) -> float:
@@ -354,7 +352,6 @@ def __init__(
self,
file: Union[StringPathLike, TextIO],
channel: int = 1,
- *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/blf.py b/can/io/blf.py
index 93fa54ca2..8d5ade8c8 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -146,7 +146,6 @@ class BLFReader(MessageReader):
def __init__(
self,
file: Union[StringPathLike, BinaryIO],
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -375,7 +374,6 @@ def __init__(
append: bool = False,
channel: int = 1,
compression_level: int = -1,
- *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/canutils.py b/can/io/canutils.py
index e159ecdf4..c57a6ca97 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -37,7 +37,6 @@ class CanutilsLogReader(MessageReader):
def __init__(
self,
file: Union[StringPathLike, TextIO],
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -137,7 +136,6 @@ def __init__(
file: Union[StringPathLike, TextIO],
channel: str = "vcan0",
append: bool = False,
- *args: Any,
**kwargs: Any,
):
"""
@@ -173,11 +171,11 @@ def on_message_received(self, msg):
framestr = f"({timestamp:f}) {channel}"
if msg.is_error_frame:
- framestr += " %08X#" % (CAN_ERR_FLAG | CAN_ERR_BUSERROR)
+ framestr += f" {CAN_ERR_FLAG | CAN_ERR_BUSERROR:08X}#"
elif msg.is_extended_id:
- framestr += " %08X#" % (msg.arbitration_id)
+ framestr += f" {msg.arbitration_id:08X}#"
else:
- framestr += " %03X#" % (msg.arbitration_id)
+ framestr += f" {msg.arbitration_id:03X}#"
if msg.is_error_frame:
eol = "\n"
@@ -193,7 +191,7 @@ def on_message_received(self, msg):
fd_flags |= CANFD_BRS
if msg.error_state_indicator:
fd_flags |= CANFD_ESI
- framestr += "#%X" % fd_flags
+ framestr += f"#{fd_flags:X}"
framestr += f"{msg.data.hex().upper()}{eol}"
self.file.write(framestr)
diff --git a/can/io/csv.py b/can/io/csv.py
index 2e2f46699..ecfc5de35 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -31,7 +31,6 @@ class CSVReader(MessageReader):
def __init__(
self,
file: Union[StringPathLike, TextIO],
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -95,7 +94,6 @@ def __init__(
self,
file: Union[StringPathLike, TextIO],
append: bool = False,
- *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/generic.py b/can/io/generic.py
index d5c7a2057..77bba4501 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -1,5 +1,5 @@
"""Contains generic base classes for file IO."""
-
+import locale
from abc import ABCMeta
from typing import (
Optional,
@@ -32,8 +32,7 @@ def __init__(
self,
file: Optional[can.typechecking.AcceptedIOType],
mode: str = "rt",
- *args: Any,
- **kwargs: Any
+ **kwargs: Any,
) -> None:
"""
:param file: a path-like object to open a file, a file-like object
@@ -45,11 +44,18 @@ def __init__(
# file is None or some file-like object
self.file = cast(Optional[can.typechecking.FileLike], file)
else:
+ encoding: Optional[str] = (
+ None
+ if "b" in mode
+ else kwargs.get("encoding", locale.getpreferredencoding(False))
+ )
# pylint: disable=consider-using-with
# file is some path-like object
self.file = cast(
can.typechecking.FileLike,
- open(cast(can.typechecking.StringPathLike, file), mode),
+ open(
+ cast(can.typechecking.StringPathLike, file), mode, encoding=encoding
+ ),
)
# for multiple inheritance
@@ -74,37 +80,30 @@ def stop(self) -> None:
self.file.close()
-# pylint: disable=abstract-method,too-few-public-methods
class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta):
"""The base class for all writers."""
file: Optional[can.typechecking.FileLike]
-# pylint: disable=abstract-method,too-few-public-methods
class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
"""A specialized base class for all writers with file descriptors."""
file: can.typechecking.FileLike
def __init__(
- self,
- file: can.typechecking.AcceptedIOType,
- mode: str = "wt",
- *args: Any,
- **kwargs: Any
+ self, file: can.typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any
) -> None:
# Not possible with the type signature, but be verbose for user-friendliness
if file is None:
raise ValueError("The given file cannot be None")
- super().__init__(file, mode)
+ super().__init__(file, mode, **kwargs)
def file_size(self) -> int:
"""Return an estimate of the current file size in bytes."""
return self.file.tell()
-# pylint: disable=too-few-public-methods
class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta):
"""The base class for all readers."""
diff --git a/can/io/logger.py b/can/io/logger.py
index a08cf9869..b6ea23380 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -27,7 +27,7 @@
from ..typechecking import StringPathLike, FileLike, AcceptedIOType
-class Logger(MessageWriter): # pylint: disable=abstract-method
+class Logger(MessageWriter):
"""
Logs CAN messages to a file.
@@ -66,7 +66,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method
@staticmethod
def __new__( # type: ignore
- cls: Any, filename: Optional[StringPathLike], *args: Any, **kwargs: Any
+ cls: Any, filename: Optional[StringPathLike], **kwargs: Any
) -> MessageWriter:
"""
:param filename: the filename/path of the file to write to,
@@ -75,7 +75,7 @@ def __new__( # type: ignore
:raises ValueError: if the filename's suffix is of an unknown file type
"""
if filename is None:
- return Printer(*args, **kwargs)
+ return Printer(**kwargs)
if not Logger.fetched_plugins:
Logger.message_writers.update(
@@ -90,19 +90,17 @@ def __new__( # type: ignore
file_or_filename: AcceptedIOType = filename
if suffix == ".gz":
- suffix, file_or_filename = Logger.compress(filename, *args, **kwargs)
+ suffix, file_or_filename = Logger.compress(filename, **kwargs)
try:
- return Logger.message_writers[suffix](file_or_filename, *args, **kwargs)
+ return Logger.message_writers[suffix](file=file_or_filename, **kwargs)
except KeyError:
raise ValueError(
f'No write support for this unknown log format "{suffix}"'
) from None
@staticmethod
- def compress(
- filename: StringPathLike, *args: Any, **kwargs: Any
- ) -> Tuple[str, FileLike]:
+ def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]:
"""
Return the suffix and io object of the decompressed file.
File will automatically recompress upon close.
@@ -154,11 +152,10 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC):
#: An integer counter to track the number of rollovers.
rollover_count: int = 0
- def __init__(self, *args: Any, **kwargs: Any) -> None:
+ def __init__(self, **kwargs: Any) -> None:
Listener.__init__(self)
- BaseIOHandler.__init__(self, None)
+ BaseIOHandler.__init__(self, file=None)
- self.writer_args = args
self.writer_kwargs = kwargs
# Expected to be set by the subclass
@@ -184,7 +181,7 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike:
if not callable(self.namer):
return default_name
- return self.namer(default_name)
+ return self.namer(default_name) # pylint: disable=not-callable
def rotate(self, source: StringPathLike, dest: StringPathLike) -> None:
"""When rotating, rotate the current log.
@@ -205,7 +202,7 @@ def rotate(self, source: StringPathLike, dest: StringPathLike) -> None:
if os.path.exists(source):
os.rename(source, dest)
else:
- self.rotator(source, dest)
+ self.rotator(source, dest) # pylint: disable=not-callable
def on_message_received(self, msg: Message) -> None:
"""This method is called to handle the given message.
@@ -234,7 +231,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower()
if suffix in self._supported_formats:
- logger = Logger(filename, *self.writer_args, **self.writer_kwargs)
+ logger = Logger(filename=filename, **self.writer_kwargs)
if isinstance(logger, FileIOMessageWriter):
return logger
elif isinstance(logger, Printer) and logger.file is not None:
@@ -323,7 +320,6 @@ def __init__(
self,
base_filename: StringPathLike,
max_bytes: int = 0,
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -334,7 +330,7 @@ def __init__(
The size threshold at which a new log file shall be created. If set to 0, no
rollover will be performed.
"""
- super().__init__(*args, **kwargs)
+ super().__init__(**kwargs)
self.base_filename = os.path.abspath(base_filename)
self.max_bytes = max_bytes
diff --git a/can/io/player.py b/can/io/player.py
index 21d1964bb..0e062ecb7 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -65,7 +65,6 @@ class LogReader(MessageReader):
def __new__( # type: ignore
cls: typing.Any,
filename: StringPathLike,
- *args: typing.Any,
**kwargs: typing.Any,
) -> MessageReader:
"""
@@ -87,7 +86,7 @@ def __new__( # type: ignore
if suffix == ".gz":
suffix, file_or_filename = LogReader.decompress(filename)
try:
- return LogReader.message_readers[suffix](file_or_filename, *args, **kwargs)
+ return LogReader.message_readers[suffix](file=file_or_filename, **kwargs)
except KeyError:
raise ValueError(
f'No read support for this unknown log format "{suffix}"'
@@ -109,7 +108,7 @@ def __iter__(self) -> typing.Generator[Message, None, None]:
raise NotImplementedError()
-class MessageSync: # pylint: disable=too-few-public-methods
+class MessageSync:
"""
Used to iterate over some given messages in the recorded time.
"""
diff --git a/can/io/printer.py b/can/io/printer.py
index 6a43c63b9..01da12e84 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -29,7 +29,6 @@ def __init__(
self,
file: Optional[Union[StringPathLike, TextIO]] = None,
append: bool = False,
- *args: Any,
**kwargs: Any
) -> None:
"""
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index 0a4de85f2..33f5d293f 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -36,7 +36,6 @@ def __init__(
self,
file: StringPathLike,
table_name: str = "messages",
- *args: Any,
**kwargs: Any,
) -> None:
"""
@@ -138,7 +137,6 @@ def __init__(
self,
file: StringPathLike,
table_name: str = "messages",
- *args: Any,
**kwargs: Any,
) -> None:
"""
diff --git a/can/io/trc.py b/can/io/trc.py
index 0a07f01c9..ec08d1af1 100644
--- a/can/io/trc.py
+++ b/can/io/trc.py
@@ -7,12 +7,12 @@
Version 1.1 will be implemented as it is most commonly used
""" # noqa
-from typing import Generator, Optional, Union, TextIO
from datetime import datetime, timedelta
from enum import Enum
-from io import TextIOWrapper
+import io
import os
import logging
+from typing import Generator, Optional, Union, TextIO, Callable, List
from ..message import Message
from ..util import channel2int
@@ -55,11 +55,14 @@ def __init__(
if not self.file:
raise ValueError("The given file cannot be None")
+ self._parse_cols: Callable[[List[str]], Optional[Message]] = lambda x: None
+
def _extract_header(self):
+ line = ""
for line in self.file:
line = line.strip()
if line.startswith(";$FILEVERSION"):
- logger.debug(f"TRCReader: Found file version '{line}'")
+ logger.debug("TRCReader: Found file version '%s'", line)
try:
file_version = line.split("=")[1]
if file_version == "1.1":
@@ -91,7 +94,7 @@ def _extract_header(self):
return line
- def _parse_msg_V1_0(self, cols):
+ def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]:
arbit_id = cols[2]
if arbit_id == "FFFFFFFF":
logger.info("TRCReader: Dropping bus info line")
@@ -106,7 +109,7 @@ def _parse_msg_V1_0(self, cols):
msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)])
return msg
- def _parse_msg_V1_1(self, cols):
+ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]:
arbit_id = cols[3]
msg = Message()
@@ -119,7 +122,7 @@ def _parse_msg_V1_1(self, cols):
msg.is_rx = cols[2] == "Rx"
return msg
- def _parse_msg_V2_1(self, cols):
+ def _parse_msg_V2_1(self, cols: List[str]) -> Optional[Message]:
msg = Message()
msg.timestamp = float(cols[1]) / 1000
msg.arbitration_id = int(cols[4], 16)
@@ -130,29 +133,29 @@ def _parse_msg_V2_1(self, cols):
msg.is_rx = cols[5] == "Rx"
return msg
- def _parse_cols_V1_1(self, cols):
+ def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]:
dtype = cols[2]
- if dtype == "Tx" or dtype == "Rx":
+ if dtype in ("Tx", "Rx"):
return self._parse_msg_V1_1(cols)
else:
- logger.info(f"TRCReader: Unsupported type '{dtype}'")
+ logger.info("TRCReader: Unsupported type '%s'", dtype)
return None
- def _parse_cols_V2_1(self, cols):
+ def _parse_cols_V2_1(self, cols: List[str]) -> Optional[Message]:
dtype = cols[2]
if dtype == "DT":
return self._parse_msg_V2_1(cols)
else:
- logger.info(f"TRCReader: Unsupported type '{dtype}'")
+ logger.info("TRCReader: Unsupported type '%s'", dtype)
return None
- def _parse_line(self, line):
- logger.debug(f"TRCReader: Parse '{line}'")
+ def _parse_line(self, line: str) -> Optional[Message]:
+ logger.debug("TRCReader: Parse '%s'", line)
try:
cols = line.split()
return self._parse_cols(cols)
except IndexError:
- logger.warning(f"TRCReader: Failed to parse message '{line}'")
+ logger.warning("TRCReader: Failed to parse message '%s'", line)
return None
def __iter__(self) -> Generator[Message, None, None]:
@@ -211,81 +214,66 @@ def __init__(
"""
super().__init__(file, mode="w")
self.channel = channel
- if type(file) is str:
- self.filepath = os.path.abspath(file)
- elif type(file) is TextIOWrapper:
- self.filepath = "Unknown"
- logger.warning("TRCWriter: Text mode io can result in wrong line endings")
- logger.debug(
- f"TRCWriter: Text mode io line ending setting: {file.newlines}"
- )
+
+ if isinstance(self.file, io.TextIOWrapper):
+ self.file.reconfigure(newline="\r\n")
else:
- self.filepath = "Unknown"
+ raise TypeError("File must be opened in text mode.")
+ self.filepath = os.path.abspath(self.file.name)
self.header_written = False
self.msgnr = 0
self.first_timestamp = None
self.file_version = TRCFileVersion.V2_1
+ self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0
self._format_message = self._format_message_init
- def _write_line(self, line: str) -> None:
- self.file.write(line + "\r\n")
-
- def _write_lines(self, lines: list) -> None:
- for line in lines:
- self._write_line(line)
-
def _write_header_V1_0(self, start_time: timedelta) -> None:
- self._write_line(
- ";##########################################################################"
- )
- self._write_line(f"; {self.filepath}")
- self._write_line(";")
- self._write_line("; Generated by python-can TRCWriter")
- self._write_line(f"; Start time: {start_time}")
- self._write_line("; PCAN-Net: N/A")
- self._write_line(";")
- self._write_line("; Columns description:")
- self._write_line("; ~~~~~~~~~~~~~~~~~~~~~")
- self._write_line("; +-current number in actual sample")
- self._write_line("; | +time offset of message (ms)")
- self._write_line("; | | +ID of message (hex)")
- self._write_line("; | | | +data length code")
- self._write_line("; | | | | +data bytes (hex) ...")
- self._write_line("; | | | | |")
- self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...")
+ lines = [
+ ";##########################################################################",
+ f"; {self.filepath}",
+ ";",
+ "; Generated by python-can TRCWriter",
+ f"; Start time: {start_time}",
+ "; PCAN-Net: N/A",
+ ";",
+ "; Columns description:",
+ "; ~~~~~~~~~~~~~~~~~~~~~",
+ "; +-current number in actual sample",
+ "; | +time offset of message (ms",
+ "; | | +ID of message (hex",
+ "; | | | +data length code",
+ "; | | | | +data bytes (hex ...",
+ "; | | | | |",
+ ";----+- ---+--- ----+--- + -+ -- -- ...",
+ ]
+ self.file.writelines(line + "\n" for line in lines)
def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None:
milliseconds = int(
(header_time.seconds * 1000) + (header_time.microseconds / 1000)
)
-
- self._write_line(";$FILEVERSION=2.1")
- self._write_line(f";$STARTTIME={header_time.days}.{milliseconds}")
- self._write_line(";$COLUMNS=N,O,T,B,I,d,R,L,D")
- self._write_line(";")
- self._write_line(f"; {self.filepath}")
- self._write_line(";")
- self._write_line(f"; Start time: {start_time}")
- self._write_line("; Generated by python-can TRCWriter")
- self._write_line(
- ";-------------------------------------------------------------------------------"
- )
- self._write_line("; Bus Name Connection Protocol")
- self._write_line("; N/A N/A N/A N/A")
- self._write_line(
- ";-------------------------------------------------------------------------------"
- )
- self._write_lines(
- [
- "; Message Time Type ID Rx/Tx",
- "; Number Offset | Bus [hex] | Reserved",
- "; | [ms] | | | | | Data Length Code",
- "; | | | | | | | | Data [hex] ...",
- "; | | | | | | | | |",
- ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --",
- ]
- )
+ lines = [
+ ";$FILEVERSION=2.1",
+ f";$STARTTIME={header_time.days}.{milliseconds}",
+ ";$COLUMNS=N,O,T,B,I,d,R,L,D",
+ ";",
+ f"; {self.filepath}",
+ ";",
+ f"; Start time: {start_time}",
+ "; Generated by python-can TRCWriter",
+ ";-------------------------------------------------------------------------------",
+ "; Bus Name Connection Protocol",
+ "; N/A N/A N/A N/A",
+ ";-------------------------------------------------------------------------------",
+ "; Message Time Type ID Rx/Tx",
+ "; Number Offset | Bus [hex] | Reserved",
+ "; | [ms] | | | | | Data Length Code",
+ "; | | | | | | | | Data [hex] ...",
+ "; | | | | | | | | |",
+ ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --",
+ ]
+ self.file.writelines(line + "\n" for line in lines)
def _format_message_by_format(self, msg, channel):
if msg.is_extended_id:
@@ -316,7 +304,7 @@ def _format_message_init(self, msg, channel):
else:
raise NotImplementedError("File format is not supported")
- return self._format_message(msg, channel)
+ return self._format_message_by_format(msg, channel)
def write_header(self, timestamp: float) -> None:
# write start of file header
@@ -336,7 +324,7 @@ def log_event(self, message: str, timestamp: float) -> None:
if not self.header_written:
self.write_header(timestamp)
- self._write_line(message)
+ self.file.write(message + "\n")
def on_message_received(self, msg: Message) -> None:
if self.first_timestamp is None:
diff --git a/can/listener.py b/can/listener.py
index 6c9cdf0be..e68d813d1 100644
--- a/can/listener.py
+++ b/can/listener.py
@@ -7,7 +7,7 @@
import asyncio
from abc import ABCMeta, abstractmethod
from queue import SimpleQueue, Empty
-from typing import Any, AsyncIterator, Awaitable, Optional
+from typing import Any, AsyncIterator, Optional
from can.message import Message
from can.bus import BusABC
@@ -126,7 +126,9 @@ def stop(self) -> None:
self.is_stopped = True
-class AsyncBufferedReader(Listener): # pylint: disable=abstract-method
+class AsyncBufferedReader(
+ Listener, AsyncIterator[Message]
+): # pylint: disable=abstract-method
"""A message buffer for use with :mod:`asyncio`.
See :ref:`asyncio` for how to use with :class:`can.Notifier`.
@@ -174,5 +176,5 @@ async def get_message(self) -> Message:
def __aiter__(self) -> AsyncIterator[Message]:
return self
- def __anext__(self) -> Awaitable[Message]:
- return self.buffer.get()
+ async def __anext__(self) -> Message:
+ return await self.buffer.get()
diff --git a/can/logconvert.py b/can/logconvert.py
index 730e82304..d89155758 100644
--- a/can/logconvert.py
+++ b/can/logconvert.py
@@ -56,7 +56,7 @@ def main():
with logger:
try:
- for m in reader: # pylint: disable=not-an-iterable
+ for m in reader:
logger(m)
except KeyboardInterrupt:
sys.exit(1)
diff --git a/can/logger.py b/can/logger.py
index f13b78bfc..55e67b27e 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -58,7 +58,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
def _append_filter_argument(
parser: Union[
argparse.ArgumentParser,
- argparse._ArgumentGroup, # pylint: disable=protected-access
+ argparse._ArgumentGroup,
],
*args: str,
**kwargs: Any,
diff --git a/can/message.py b/can/message.py
index 8e0c4deee..48933b2da 100644
--- a/can/message.py
+++ b/can/message.py
@@ -228,9 +228,7 @@ def __deepcopy__(self, memo: dict) -> "Message":
error_state_indicator=self.error_state_indicator,
)
- def _check(
- self,
- ) -> None: # pylint: disable=too-many-branches; it's still simple code
+ def _check(self) -> None:
"""Checks if the message parameters are valid.
Assumes that the attribute types are already correct.
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 2952103c3..28bcf2aa2 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,4 +1,4 @@
-pylint==2.12.2
+pylint==2.15.9
black~=22.10.0
mypy==0.991
mypy-extensions==0.4.3
From 7013796ad8341e627ea0f46cada2a82506be4562 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 2 Jan 2023 00:40:55 +0100
Subject: [PATCH 213/475] add `ignore_config` parameter to can.Bus
---
can/interface.py | 45 +++++++++++++++++++++++++--------------------
test/test_bus.py | 14 ++++++++++++++
2 files changed, 39 insertions(+), 20 deletions(-)
create mode 100644 test/test_bus.py
diff --git a/can/interface.py b/can/interface.py
index 76d0dd1a5..83ead4021 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -8,8 +8,8 @@
import logging
from typing import Any, cast, Iterable, Type, Optional, Union, List
+from . import util
from .bus import BusABC
-from .util import load_config, deprecated_args_alias
from .interfaces import BACKENDS
from .exceptions import CanInterfaceNotImplementedError
from .typechecking import AutoDetectedConfig, Channel
@@ -61,6 +61,13 @@ class Bus(BusABC): # pylint: disable=abstract-method
Instantiates a CAN Bus of the given ``interface``, falls back to reading a
configuration file from default locations.
+ .. note::
+ Please note that while the arguments provided to this class take precedence
+ over any existing values from configuration, it is possible that other parameters
+ from the configuration may be added to the bus instantiation.
+ This could potentially have unintended consequences. To prevent this,
+ you may use the *ignore_config* parameter to ignore any existing configurations.
+
:param channel:
Channel identification. Expected type is backend dependent.
Set to ``None`` to let it be resolved automatically from the default
@@ -71,8 +78,13 @@ class Bus(BusABC): # pylint: disable=abstract-method
Set to ``None`` to let it be resolved automatically from the default
:ref:`configuration`.
- :param args:
- ``interface`` specific positional arguments.
+ :param context:
+ Extra 'context', that is passed to config sources.
+ This can be used to select a section other than 'default' in the configuration file.
+
+ :param ignore_config:
+ If ``True``, only the given arguments will be used for the bus instantiation. Existing
+ configuration sources will be ignored.
:param kwargs:
``interface`` specific keyword arguments.
@@ -88,12 +100,13 @@ class Bus(BusABC): # pylint: disable=abstract-method
"""
@staticmethod
- @deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2
- def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
+ @util.deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2
+ def __new__( # type: ignore
cls: Any,
channel: Optional[Channel] = None,
interface: Optional[str] = None,
- *args: Any,
+ context: Optional[str] = None,
+ ignore_config: bool = False,
**kwargs: Any,
) -> BusABC:
# figure out the rest of the configuration; this might raise an error
@@ -101,12 +114,9 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
kwargs["interface"] = interface
if channel is not None:
kwargs["channel"] = channel
- if "context" in kwargs:
- context = kwargs["context"]
- del kwargs["context"]
- else:
- context = None
- kwargs = load_config(config=kwargs, context=context)
+
+ if not ignore_config:
+ kwargs = util.load_config(config=kwargs, context=context)
# resolve the bus class to use for that interface
cls = _get_class_for_interface(kwargs["interface"])
@@ -115,17 +125,12 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
del kwargs["interface"]
# make sure the bus can handle this config format
- if "channel" not in kwargs:
- raise ValueError("'channel' argument missing")
- else:
- channel = kwargs["channel"]
- del kwargs["channel"]
-
+ channel = kwargs.pop("channel", channel)
if channel is None:
# Use the default channel for the backend
- bus = cls(*args, **kwargs)
+ bus = cls(**kwargs)
else:
- bus = cls(channel, *args, **kwargs)
+ bus = cls(channel, **kwargs)
return cast(BusABC, bus)
diff --git a/test/test_bus.py b/test/test_bus.py
new file mode 100644
index 000000000..e11d829d3
--- /dev/null
+++ b/test/test_bus.py
@@ -0,0 +1,14 @@
+from unittest.mock import patch
+
+import can
+
+
+def test_bus_ignore_config():
+ with patch.object(
+ target=can.util, attribute="load_config", side_effect=can.util.load_config
+ ):
+ _ = can.Bus(interface="virtual", ignore_config=True)
+ assert not can.util.load_config.called
+
+ _ = can.Bus(interface="virtual")
+ assert can.util.load_config.called
From 2f50efdc03960d5247dff5c4be957d26458912bb Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 2 Jan 2023 12:38:17 +0100
Subject: [PATCH 214/475] rename context -> config_context
---
can/interface.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/can/interface.py b/can/interface.py
index 83ead4021..2bc8820f5 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -78,7 +78,7 @@ class Bus(BusABC): # pylint: disable=abstract-method
Set to ``None`` to let it be resolved automatically from the default
:ref:`configuration`.
- :param context:
+ :param config_context:
Extra 'context', that is passed to config sources.
This can be used to select a section other than 'default' in the configuration file.
@@ -100,12 +100,14 @@ class Bus(BusABC): # pylint: disable=abstract-method
"""
@staticmethod
- @util.deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2
+ @util.deprecated_args_alias( # Deprecated since python-can 4.2
+ bustype="interface", context="config_context"
+ )
def __new__( # type: ignore
cls: Any,
channel: Optional[Channel] = None,
interface: Optional[str] = None,
- context: Optional[str] = None,
+ config_context: Optional[str] = None,
ignore_config: bool = False,
**kwargs: Any,
) -> BusABC:
@@ -116,7 +118,7 @@ def __new__( # type: ignore
kwargs["channel"] = channel
if not ignore_config:
- kwargs = util.load_config(config=kwargs, context=context)
+ kwargs = util.load_config(config=kwargs, context=config_context)
# resolve the bus class to use for that interface
cls = _get_class_for_interface(kwargs["interface"])
From 1278a0f1accde270b7fdd5d1ff9127456820a794 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 4 Jan 2023 20:27:30 +0100
Subject: [PATCH 215/475] Run doctest in CI (#1476)
---
.github/workflows/ci.yml | 5 ++++-
can/bus.py | 10 ++++++----
can/exceptions.py | 24 +++++++++++++++---------
can/interfaces/kvaser/canlib.py | 15 ++++++++++++---
doc/interfaces/ixxat.rst | 19 ++++++++++++++-----
doc/message.rst | 20 ++++++++++----------
6 files changed, 61 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6a5c4253..c610c6a76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -129,7 +129,10 @@ jobs:
pip install -r doc/doc-requirements.txt
- name: Build documentation
run: |
- python -m sphinx -Wan doc build
+ python -m sphinx -Wan --keep-going doc build
+ - name: Run doctest
+ run: |
+ python -m sphinx -b doctest -W --keep-going doc build
- uses: actions/upload-artifact@v3
with:
name: sphinx-out
diff --git a/can/bus.py b/can/bus.py
index d0192fc2d..f29b8ea6e 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -314,8 +314,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None:
def __iter__(self) -> Iterator[Message]:
"""Allow iteration on messages as they are received.
- >>> for msg in bus:
- ... print(msg)
+ .. code-block:: python
+
+ for msg in bus:
+ print(msg)
:yields:
@@ -352,9 +354,9 @@ def set_filters(
:param filters:
A iterable of dictionaries each containing a "can_id",
- a "can_mask", and an optional "extended" key.
+ a "can_mask", and an optional "extended" key::
- >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
+ [{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
A filter matches, when
`` & can_mask == can_id & can_mask``.
diff --git a/can/exceptions.py b/can/exceptions.py
index 7496c6c0e..57130082a 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -32,15 +32,21 @@ class CanError(Exception):
If specified, the error code is automatically appended to the message:
- >>> # With an error code (it also works with a specific error):
- >>> error = CanOperationError(message="Failed to do the thing", error_code=42)
- >>> str(error)
- 'Failed to do the thing [Error Code 42]'
- >>>
- >>> # Missing the error code:
- >>> plain_error = CanError(message="Something went wrong ...")
- >>> str(plain_error)
- 'Something went wrong ...'
+ .. testsetup:: canerror
+
+ from can import CanError, CanOperationError
+
+ .. doctest:: canerror
+
+ >>> # With an error code (it also works with a specific error):
+ >>> error = CanOperationError(message="Failed to do the thing", error_code=42)
+ >>> str(error)
+ 'Failed to do the thing [Error Code 42]'
+ >>>
+ >>> # Missing the error code:
+ >>> plain_error = CanError(message="Something went wrong ...")
+ >>> str(plain_error)
+ 'Something went wrong ...'
:param error_code:
An optional error code to narrow down the cause of the fault
diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py
index a8bb7bac7..2bbf8f0bf 100644
--- a/can/interfaces/kvaser/canlib.py
+++ b/can/interfaces/kvaser/canlib.py
@@ -662,9 +662,18 @@ def get_stats(self) -> structures.BusStatistics:
Use like so:
- >>> stats = bus.get_stats()
- >>> print(stats)
- std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0
+ .. testsetup:: kvaser
+
+ from unittest.mock import Mock
+ from can.interfaces.kvaser.structures import BusStatistics
+ bus = Mock()
+ bus.get_stats = Mock(side_effect=lambda: BusStatistics())
+
+ .. doctest:: kvaser
+
+ >>> stats = bus.get_stats()
+ >>> print(stats)
+ std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0
:returns: bus statistics.
"""
diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst
index 61df70638..f73a01036 100644
--- a/doc/interfaces/ixxat.rst
+++ b/doc/interfaces/ixxat.rst
@@ -59,11 +59,20 @@ List available devices
In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id.
To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below:
- >>> from can.interfaces.ixxat import get_ixxat_hwids
- >>> for hwid in get_ixxat_hwids():
- ... print("Found IXXAT with hardware id '%s'." % hwid)
- Found IXXAT with hardware id 'HW441489'.
- Found IXXAT with hardware id 'HW107422'.
+ .. testsetup:: ixxat
+
+ from unittest.mock import Mock
+ import can.interfaces.ixxat
+ assert hasattr(can.interfaces.ixxat, "get_ixxat_hwids")
+ can.interfaces.ixxat.get_ixxat_hwids = Mock(side_effect=lambda: ['HW441489', 'HW107422'])
+
+ .. doctest:: ixxat
+
+ >>> from can.interfaces.ixxat import get_ixxat_hwids
+ >>> for hwid in get_ixxat_hwids():
+ ... print("Found IXXAT with hardware id '%s'." % hwid)
+ Found IXXAT with hardware id 'HW441489'.
+ Found IXXAT with hardware id 'HW107422'.
Bus
diff --git a/doc/message.rst b/doc/message.rst
index 78ccc0b50..d47473e17 100644
--- a/doc/message.rst
+++ b/doc/message.rst
@@ -15,7 +15,7 @@ Message
>>> test.dlc
5
>>> print(test)
- Timestamp: 0.000000 ID: 00000000 010 DLC: 5 01 02 03 04 05
+ Timestamp: 0.000000 ID: 00000000 X Rx DL: 5 01 02 03 04 05
The :attr:`~can.Message.arbitration_id` field in a CAN message may be either
@@ -44,7 +44,7 @@ Message
2\ :sup:`29` - 1 for 29-bit identifiers).
>>> print(Message(is_extended_id=False, arbitration_id=100))
- Timestamp: 0.000000 ID: 0064 S DLC: 0
+ Timestamp: 0.000000 ID: 0064 S Rx DL: 0
.. attribute:: data
@@ -56,7 +56,7 @@ Message
>>> example_data = bytearray([1, 2, 3])
>>> print(Message(data=example_data))
- Timestamp: 0.000000 ID: 00000000 X DLC: 3 01 02 03
+ Timestamp: 0.000000 ID: 00000000 X Rx DL: 3 01 02 03
A :class:`~can.Message` can also be created with bytes, or lists of ints:
@@ -106,9 +106,9 @@ Message
Previously this was exposed as `id_type`.
>>> print(Message(is_extended_id=False))
- Timestamp: 0.000000 ID: 0000 S DLC: 0
+ Timestamp: 0.000000 ID: 0000 S Rx DL: 0
>>> print(Message(is_extended_id=True))
- Timestamp: 0.000000 ID: 00000000 X DLC: 0
+ Timestamp: 0.000000 ID: 00000000 X Rx DL: 0
.. note::
@@ -124,7 +124,7 @@ Message
This boolean parameter indicates if the message is an error frame or not.
>>> print(Message(is_error_frame=True))
- Timestamp: 0.000000 ID: 00000000 X E DLC: 0
+ Timestamp: 0.000000 ID: 00000000 X Rx E DL: 0
.. attribute:: is_remote_frame
@@ -135,7 +135,7 @@ Message
modifies the bit in the CAN message's flags field indicating this.
>>> print(Message(is_remote_frame=True))
- Timestamp: 0.000000 ID: 00000000 X R DLC: 0
+ Timestamp: 0.000000 ID: 00000000 X Rx R DL: 0
.. attribute:: is_fd
@@ -174,17 +174,17 @@ Message
>>> from can import Message
>>> test = Message()
>>> print(test)
- Timestamp: 0.000000 ID: 00000000 X DLC: 0
+ Timestamp: 0.000000 ID: 00000000 X Rx DL: 0
>>> test2 = Message(data=[1, 2, 3, 4, 5])
>>> print(test2)
- Timestamp: 0.000000 ID: 00000000 X DLC: 5 01 02 03 04 05
+ Timestamp: 0.000000 ID: 00000000 X Rx DL: 5 01 02 03 04 05
The fields in the printed message are (in order):
- timestamp,
- arbitration ID,
- flags,
- - dlc,
+ - data length (DL),
- and data.
From 5e5b950228302c21529ca1bb6f234f9bd7e0a372 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 5 Jan 2023 23:36:03 +0100
Subject: [PATCH 216/475] add deprecation period to deprecated_args_alias
(#1477)
---
can/interface.py | 7 +-
can/interfaces/ixxat/canlib_vcinpl.py | 2 +
can/interfaces/ixxat/canlib_vcinpl2.py | 2 +
can/interfaces/vector/canlib.py | 6 +-
can/util.py | 53 +++++++++++---
test/test_util.py | 95 +++++++++++++++++++++++---
6 files changed, 144 insertions(+), 21 deletions(-)
diff --git a/can/interface.py b/can/interface.py
index 2bc8820f5..04fc84ae9 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -100,8 +100,11 @@ class Bus(BusABC): # pylint: disable=abstract-method
"""
@staticmethod
- @util.deprecated_args_alias( # Deprecated since python-can 4.2
- bustype="interface", context="config_context"
+ @util.deprecated_args_alias(
+ deprecation_start="4.2.0",
+ deprecation_end="5.0.0",
+ bustype="interface",
+ context="config_context",
)
def __new__( # type: ignore
cls: Any,
diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py
index d74da2539..8304a6dd7 100644
--- a/can/interfaces/ixxat/canlib_vcinpl.py
+++ b/can/interfaces/ixxat/canlib_vcinpl.py
@@ -417,6 +417,8 @@ class IXXATBus(BusABC):
}
@deprecated_args_alias(
+ deprecation_start="4.0.0",
+ deprecation_end="5.0.0",
UniqueHardwareId="unique_hardware_id",
rxFifoSize="rx_fifo_size",
txFifoSize="tx_fifo_size",
diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py
index 2e3125e9b..b8ed916dc 100644
--- a/can/interfaces/ixxat/canlib_vcinpl2.py
+++ b/can/interfaces/ixxat/canlib_vcinpl2.py
@@ -417,6 +417,8 @@ class IXXATBus(BusABC):
"""
@deprecated_args_alias(
+ deprecation_start="4.0.0",
+ deprecation_end="5.0.0",
UniqueHardwareId="unique_hardware_id",
rxFifoSize="rx_fifo_size",
txFifoSize="tx_fifo_size",
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index ff72d262a..7c1ffac64 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -75,7 +75,11 @@ class VectorBus(BusABC):
tseg2Dbr="tseg2_dbr",
)
- @deprecated_args_alias(**deprecated_args)
+ @deprecated_args_alias(
+ deprecation_start="4.0.0",
+ deprecation_end="5.0.0",
+ **deprecated_args,
+ )
def __init__(
self,
channel: Union[int, Sequence[int], str],
diff --git a/can/util.py b/can/util.py
index aa4a28d15..e4a45f2d5 100644
--- a/can/util.py
+++ b/can/util.py
@@ -295,26 +295,47 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]:
return None
-def deprecated_args_alias(**aliases):
+def deprecated_args_alias( # type: ignore
+ deprecation_start: str, deprecation_end: Optional[str] = None, **aliases
+):
"""Allows to rename/deprecate a function kwarg(s) and optionally
have the deprecated kwarg(s) set as alias(es)
Example::
- @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg")
+ @deprecated_args_alias("1.2.0", oldArg="new_arg", anotherOldArg="another_new_arg")
def library_function(new_arg, another_new_arg):
pass
- @deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None)
+ @deprecated_args_alias(
+ deprecation_start="1.2.0",
+ deprecation_end="3.0.0",
+ oldArg="new_arg",
+ obsoleteOldArg=None,
+ )
def library_function(new_arg):
pass
+ :param deprecation_start:
+ The *python-can* version, that introduced the :class:`DeprecationWarning`.
+ :param deprecation_end:
+ The *python-can* version, that marks the end of the deprecation period.
+ :param aliases:
+ keyword arguments, that map the deprecated argument names
+ to the new argument names or ``None``.
+
"""
def deco(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
- _rename_kwargs(f.__name__, kwargs, aliases)
+ _rename_kwargs(
+ func_name=f.__name__,
+ start=deprecation_start,
+ end=deprecation_end,
+ kwargs=kwargs,
+ aliases=aliases,
+ )
return f(*args, **kwargs)
return wrapper
@@ -323,21 +344,35 @@ def wrapper(*args, **kwargs):
def _rename_kwargs(
- func_name: str, kwargs: Dict[str, str], aliases: Dict[str, str]
+ func_name: str,
+ start: str,
+ end: Optional[str],
+ kwargs: Dict[str, str],
+ aliases: Dict[str, str],
) -> None:
"""Helper function for `deprecated_args_alias`"""
for alias, new in aliases.items():
if alias in kwargs:
+ deprecation_notice = (
+ f"The '{alias}' argument is deprecated since python-can v{start}"
+ )
+ if end:
+ deprecation_notice += (
+ f", and scheduled for removal in python-can v{end}"
+ )
+ deprecation_notice += "."
+
value = kwargs.pop(alias)
if new is not None:
- warnings.warn(f"{alias} is deprecated; use {new}", DeprecationWarning)
+ deprecation_notice += f" Use '{new}' instead."
+
if new in kwargs:
raise TypeError(
- f"{func_name} received both {alias} (deprecated) and {new}"
+ f"{func_name} received both '{alias}' (deprecated) and '{new}'."
)
kwargs[new] = value
- else:
- warnings.warn(f"{alias} is deprecated", DeprecationWarning)
+
+ warnings.warn(deprecation_notice, DeprecationWarning)
def time_perfcounter_correlation() -> Tuple[float, float]:
diff --git a/test/test_util.py b/test/test_util.py
index 7048d6151..70941f23f 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -3,17 +3,26 @@
import unittest
import warnings
-from can.util import _create_bus_config, _rename_kwargs, channel2int
+import pytest
+
+from can.util import (
+ _create_bus_config,
+ _rename_kwargs,
+ channel2int,
+ deprecated_args_alias,
+)
class RenameKwargsTest(unittest.TestCase):
expected_kwargs = dict(a=1, b=2, c=3, d=4)
- def _test(self, kwargs, aliases):
+ def _test(self, start: str, end: str, kwargs, aliases):
# Test that we do get the DeprecationWarning when called with deprecated kwargs
- with self.assertWarnsRegex(DeprecationWarning, "is deprecated"):
- _rename_kwargs("unit_test", kwargs, aliases)
+ with self.assertWarnsRegex(
+ DeprecationWarning, "is deprecated.*?" + start + ".*?" + end
+ ):
+ _rename_kwargs("unit_test", start, end, kwargs, aliases)
# Test that the aliases contains the deprecated values and
# the obsolete kwargs have been removed
@@ -25,30 +34,98 @@ def _test(self, kwargs, aliases):
# Cause all warnings to always be triggered.
warnings.simplefilter("error", DeprecationWarning)
try:
- _rename_kwargs("unit_test", kwargs, aliases)
+ _rename_kwargs("unit_test", start, end, kwargs, aliases)
finally:
warnings.resetwarnings()
def test_rename(self):
kwargs = dict(old_a=1, old_b=2, c=3, d=4)
aliases = {"old_a": "a", "old_b": "b"}
- self._test(kwargs, aliases)
+ self._test("1.0", "2.0", kwargs, aliases)
def test_obsolete(self):
kwargs = dict(a=1, b=2, c=3, d=4, z=10)
aliases = {"z": None}
- self._test(kwargs, aliases)
+ self._test("1.0", "2.0", kwargs, aliases)
def test_rename_and_obsolete(self):
kwargs = dict(old_a=1, old_b=2, c=3, d=4, z=10)
aliases = {"old_a": "a", "old_b": "b", "z": None}
- self._test(kwargs, aliases)
+ self._test("1.0", "2.0", kwargs, aliases)
def test_with_new_and_alias_present(self):
kwargs = dict(old_a=1, a=1, b=2, c=3, d=4, z=10)
aliases = {"old_a": "a", "old_b": "b", "z": None}
with self.assertRaises(TypeError):
- self._test(kwargs, aliases)
+ self._test("1.0", "2.0", kwargs, aliases)
+
+
+class DeprecatedArgsAliasTest(unittest.TestCase):
+ def test_decorator(self):
+ @deprecated_args_alias("1.0.0", old_a="a")
+ def _test_func1(a):
+ pass
+
+ with pytest.warns(DeprecationWarning) as record:
+ _test_func1(old_a=1)
+ assert len(record) == 1
+ assert (
+ record[0].message.args[0]
+ == "The 'old_a' argument is deprecated since python-can v1.0.0. Use 'a' instead."
+ )
+
+ @deprecated_args_alias("1.6.0", "3.4.0", old_a="a", old_b=None)
+ def _test_func2(a):
+ pass
+
+ with pytest.warns(DeprecationWarning) as record:
+ _test_func2(old_a=1, old_b=2)
+ assert len(record) == 2
+ assert record[0].message.args[0] == (
+ "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled for "
+ "removal in python-can v3.4.0. Use 'a' instead."
+ )
+ assert record[1].message.args[0] == (
+ "The 'old_b' argument is deprecated since python-can v1.6.0, and scheduled for "
+ "removal in python-can v3.4.0."
+ )
+
+ @deprecated_args_alias("1.6.0", "3.4.0", old_a="a")
+ @deprecated_args_alias("2.0.0", "4.0.0", old_b=None)
+ def _test_func3(a):
+ pass
+
+ with pytest.warns(DeprecationWarning) as record:
+ _test_func3(old_a=1, old_b=2)
+ assert len(record) == 2
+ assert record[0].message.args[0] == (
+ "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled "
+ "for removal in python-can v3.4.0. Use 'a' instead."
+ )
+ assert record[1].message.args[0] == (
+ "The 'old_b' argument is deprecated since python-can v2.0.0, and scheduled "
+ "for removal in python-can v4.0.0."
+ )
+
+ with pytest.warns(DeprecationWarning) as record:
+ _test_func3(old_a=1)
+ assert len(record) == 1
+ assert record[0].message.args[0] == (
+ "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled "
+ "for removal in python-can v3.4.0. Use 'a' instead."
+ )
+
+ with pytest.warns(DeprecationWarning) as record:
+ _test_func3(a=1, old_b=2)
+ assert len(record) == 1
+ assert record[0].message.args[0] == (
+ "The 'old_b' argument is deprecated since python-can v2.0.0, and scheduled "
+ "for removal in python-can v4.0.0."
+ )
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ _test_func3(a=1)
class TestBusConfig(unittest.TestCase):
From 15b8af00af5869f7ed6b48f3e022576caab169ab Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Sat, 7 Jan 2023 07:01:47 +0100
Subject: [PATCH 217/475] Use ip link JSON output for SocketCAN
find_available_interfaces (#1478)
* Update find_available_interfaces to use ip link JSON output
* Add unit test with known JSON output for find_available_interfaces
* Add except clause for JSON decoding
* Fix pylint complaints
---
can/interfaces/socketcan/utils.py | 49 ++++++++++++++--------------
test/test_socketcan_helpers.py | 54 +++++++++++++++++++++++++------
2 files changed, 69 insertions(+), 34 deletions(-)
diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py
index 25f04617f..7a8538135 100644
--- a/can/interfaces/socketcan/utils.py
+++ b/can/interfaces/socketcan/utils.py
@@ -3,15 +3,15 @@
"""
import errno
+import json
import logging
import os
-import re
import struct
import subprocess
-from typing import cast, Iterable, Optional
+from typing import cast, Optional, List
-from can.interfaces.socketcan.constants import CAN_EFF_FLAG
from can import typechecking
+from can.interfaces.socketcan.constants import CAN_EFF_FLAG
log = logging.getLogger(__name__)
@@ -38,35 +38,36 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes
return struct.pack(can_filter_fmt, *filter_data)
-_PATTERN_CAN_INTERFACE = re.compile(r"(sl|v|vx)?can\d+")
-
+def find_available_interfaces() -> List[str]:
+ """Returns the names of all open can/vcan interfaces
-def find_available_interfaces() -> Iterable[str]:
- """Returns the names of all open can/vcan interfaces using
- the ``ip link list`` command. If the lookup fails, an error
+ The function calls the ``ip link list`` command. If the lookup fails, an error
is logged to the console and an empty list is returned.
+
+ :return: The list of available and active CAN interfaces or an empty list of the command failed
"""
try:
- # adding "type vcan" would exclude physical can devices
- command = ["ip", "-o", "link", "list", "up"]
- output = subprocess.check_output(command, text=True)
-
- except Exception as e: # pylint: disable=broad-except
+ command = ["ip", "-json", "link", "list", "up"]
+ output_str = subprocess.check_output(command, text=True)
+ except Exception: # pylint: disable=broad-except
# subprocess.CalledProcessError is too specific
- log.error("failed to fetch opened can devices: %s", e)
+ log.exception("failed to fetch opened can devices from ip link")
return []
- else:
- # log.debug("find_available_interfaces(): output=\n%s", output)
- # output contains some lines like "1: vcan42: ..."
- # extract the "vcan42" of each line
- interfaces = [line.split(": ", 3)[1] for line in output.splitlines()]
- log.debug(
- "find_available_interfaces(): detected these interfaces (before filtering): %s",
- interfaces,
- )
- return filter(_PATTERN_CAN_INTERFACE.match, interfaces)
+ try:
+ output_json = json.loads(output_str)
+ except json.JSONDecodeError:
+ log.exception("Failed to parse ip link JSON output: %s", output_str)
+ return []
+
+ log.debug(
+ "find_available_interfaces(): detected these interfaces (before filtering): %s",
+ output_json,
+ )
+
+ interfaces = [i["ifname"] for i in output_json if i["link_type"] == "can"]
+ return interfaces
def error_code_to_str(code: Optional[int]) -> str:
diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py
index ad53836f2..29ceb11c0 100644
--- a/test/test_socketcan_helpers.py
+++ b/test/test_socketcan_helpers.py
@@ -4,7 +4,12 @@
Tests helpers in `can.interfaces.socketcan.socketcan_common`.
"""
+import gzip
+from base64 import b64decode
import unittest
+from unittest import mock
+
+from subprocess import CalledProcessError
from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str
@@ -26,17 +31,46 @@ 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")
+ @unittest.skipUnless(
+ TEST_INTERFACE_SOCKETCAN, "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.assertRegex(entry, r"(sl|v|vx)?can\d+")
- if TEST_INTERFACE_SOCKETCAN:
- self.assertGreaterEqual(len(result), 3)
- self.assertIn("vcan0", result)
- self.assertIn("vxcan0", result)
- self.assertIn("slcan0", result)
+ result = find_available_interfaces()
+
+ self.assertGreaterEqual(len(result), 3)
+ self.assertIn("vcan0", result)
+ self.assertIn("vxcan0", result)
+ self.assertIn("slcan0", result)
+
+ def test_find_available_interfaces_w_patch(self):
+ # Contains lo, eth0, wlan0, vcan0, mycustomCan123
+ ip_output_gz_b64 = (
+ "H4sIAAAAAAAAA+2UzW+CMBjG7/wVhrNL+BC29IboEqNSwzQejDEViiMC5aNsmmX/+wpZTGUwDAcP"
+ "y5qmh+d5++bN80u7EXpsfZRnsUTf8yMXn0TQk/u8GqEQM1EMiMjpXoAOGZM3F6mUZxAuhoY55UpL"
+ "fbWoKjO4Hts7pl/kLdc+pDlrrmuaqnNq4vqZU8wSkSTHOeYHIjFOM4poOevKmlpwbfF+4EfHkLil"
+ "PRo/G6vZkrcPKcnjwnOxh/KA8h49JQGOimAkSaq03NFz/B0PiffIOfIXkeumOCtiEiUJXG++bp8S"
+ "5Dooo/WVZeFnvxmYUgsM01fpBmQWfDAN256M7SqioQ2NkWm8LKvGnIU3qTN+xylrV/FdaHrJzmFk"
+ "gkacozuzZMnhtAGkLANFAaoKBgOgaUDXG0F6Hrje7SDVWpDvAYpuIdmJV4dn2cSx9VUuGiFCe25Y"
+ "fwTi4KmW4ptzG0ULGvYPLN1APSqdMN3/82TRtOeqSbW5hmcnzygJTRTJivofcEvAgrAVvgD8aLkv"
+ "/AcAAA=="
+ )
+ ip_output = gzip.decompress(b64decode(ip_output_gz_b64)).decode("ascii")
+
+ with mock.patch("subprocess.check_output") as check_output:
+ check_output.return_value = ip_output
+ ifs = find_available_interfaces()
+
+ self.assertEqual(["vcan0", "mycustomCan123"], ifs)
+
+ def test_find_available_interfaces_exception(self):
+ with mock.patch("subprocess.check_output") as check_output:
+ check_output.return_value = "Not JSON
"
+ result = find_available_interfaces()
+ self.assertEqual([], result)
+
+ check_output.side_effect = Exception("Something went wrong :-/")
+ result = find_available_interfaces()
+ self.assertEqual([], result)
if __name__ == "__main__":
From c4c396b9a2441606a40c21ab0dedb54278e70149 Mon Sep 17 00:00:00 2001
From: pierreluctg
Date: Wed, 11 Jan 2023 10:33:45 -0500
Subject: [PATCH 218/475] Avoid using root logger in usb2can (#1483)
Replace the logger used to raise import error in usb2can serial selector
---
can/interfaces/usb2can/serial_selector.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py
index c6b9053d6..22a95ae7c 100644
--- a/can/interfaces/usb2can/serial_selector.py
+++ b/can/interfaces/usb2can/serial_selector.py
@@ -4,10 +4,12 @@
import logging
from typing import List
+log = logging.getLogger("can.usb2can")
+
try:
import win32com.client
except ImportError:
- logging.warning("win32com.client module required for usb2can")
+ log.warning("win32com.client module required for usb2can")
raise
From fabcdf7d175a320fa63b0f515ac7f5adcd4c184c Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 12 Jan 2023 17:55:17 +0100
Subject: [PATCH 219/475] Vector: Check sample point instead of tseg & sjw
(#1486)
* check sample point instead of tseg & sjw
* improve error message
---
can/interfaces/vector/canlib.py | 310 ++++++++++++++++++++------------
test/test_vector.py | 4 +-
2 files changed, 196 insertions(+), 118 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 7c1ffac64..5c7f1a8ad 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -148,7 +148,8 @@ def __init__(
If the bus could not be set up.
This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`.
"""
- if os.name != "nt" and not kwargs.get("_testing", False):
+ self.__testing = kwargs.get("_testing", False)
+ if os.name != "nt" and not self.__testing:
raise CanInterfaceNotImplementedError(
f"The Vector interface is only supported on Windows, "
f'but you are running "{os.name}"'
@@ -232,66 +233,20 @@ def __init__(
# set CAN settings
for channel in self.channels:
- if self._has_init_access(channel):
- if fd:
- self._set_bitrate_canfd(
- channel=channel,
- bitrate=bitrate,
- data_bitrate=data_bitrate,
- sjw_abr=sjw_abr,
- tseg1_abr=tseg1_abr,
- tseg2_abr=tseg2_abr,
- sjw_dbr=sjw_dbr,
- tseg1_dbr=tseg1_dbr,
- tseg2_dbr=tseg2_dbr,
- )
- elif bitrate:
- self._set_bitrate_can(channel=channel, bitrate=bitrate)
-
- # Check CAN settings
- for channel in self.channels:
- if kwargs.get("_testing", False):
- # avoid check if xldriver is mocked for testing
- break
-
- bus_params = self._read_bus_params(channel)
if fd:
- _canfd = bus_params.canfd
- if not all(
- [
- bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
- _canfd.can_op_mode
- & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD,
- _canfd.bitrate == bitrate if bitrate else True,
- _canfd.sjw_abr == sjw_abr if bitrate else True,
- _canfd.tseg1_abr == tseg1_abr if bitrate else True,
- _canfd.tseg2_abr == tseg2_abr if bitrate else True,
- _canfd.data_bitrate == data_bitrate if data_bitrate else True,
- _canfd.sjw_dbr == sjw_dbr if data_bitrate else True,
- _canfd.tseg1_dbr == tseg1_dbr if data_bitrate else True,
- _canfd.tseg2_dbr == tseg2_dbr if data_bitrate else True,
- ]
- ):
- raise CanInitializationError(
- f"The requested CAN FD settings could not be set for channel {channel}. "
- f"Another application might have set incompatible settings. "
- f"These are the currently active settings: {_canfd._asdict()}"
- )
- else:
- _can = bus_params.can
- if not all(
- [
- bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN,
- _can.can_op_mode
- & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20,
- _can.bitrate == bitrate if bitrate else True,
- ]
- ):
- raise CanInitializationError(
- f"The requested CAN settings could not be set for channel {channel}. "
- f"Another application might have set incompatible settings. "
- f"These are the currently active settings: {_can._asdict()}"
- )
+ self._set_bitrate_canfd(
+ channel=channel,
+ bitrate=bitrate,
+ data_bitrate=data_bitrate,
+ sjw_abr=sjw_abr,
+ tseg1_abr=tseg1_abr,
+ tseg2_abr=tseg2_abr,
+ sjw_dbr=sjw_dbr,
+ tseg1_dbr=tseg1_dbr,
+ tseg2_dbr=tseg2_dbr,
+ )
+ elif bitrate:
+ self._set_bitrate_can(channel=channel, bitrate=bitrate)
# Enable/disable TX receipts
tx_receipts = 1 if receive_own_messages else 0
@@ -422,32 +377,85 @@ def _set_bitrate_can(
)
# set parameters if channel has init access
- if any(kwargs):
- chip_params = xlclass.XLchipParams()
- chip_params.bitRate = bitrate
- chip_params.sjw = sjw
- chip_params.tseg1 = tseg1
- chip_params.tseg2 = tseg2
- chip_params.sam = sam
- self.xldriver.xlCanSetChannelParams(
- self.port_handle,
- self.channel_masks[channel],
- chip_params,
+ if self._has_init_access(channel):
+ if any(kwargs):
+ chip_params = xlclass.XLchipParams()
+ chip_params.bitRate = bitrate
+ chip_params.sjw = sjw
+ chip_params.tseg1 = tseg1
+ chip_params.tseg2 = tseg2
+ chip_params.sam = sam
+ self.xldriver.xlCanSetChannelParams(
+ self.port_handle,
+ self.channel_masks[channel],
+ chip_params,
+ )
+ LOG.info(
+ "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
+ chip_params.bitRate,
+ chip_params.sjw,
+ chip_params.tseg1,
+ chip_params.tseg2,
+ )
+ else:
+ self.xldriver.xlCanSetChannelBitrate(
+ self.port_handle,
+ self.channel_masks[channel],
+ bitrate,
+ )
+ LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate)
+
+ if self.__testing:
+ return
+
+ # Compare requested CAN settings to active settings
+ bus_params = self._read_bus_params(channel)
+ settings_acceptable = True
+
+ # check bus type
+ settings_acceptable &= (
+ bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN
+ )
+
+ # check CAN operation mode. For CANcaseXL can_op_mode remains 0
+ if bus_params.can.can_op_mode != 0:
+ settings_acceptable &= bool(
+ bus_params.can.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20
)
- LOG.info(
- "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
- chip_params.bitRate,
- chip_params.sjw,
- chip_params.tseg1,
- chip_params.tseg2,
+
+ # check bitrate
+ settings_acceptable &= abs(bus_params.can.bitrate - bitrate) < bitrate / 256
+
+ # check sample point
+ if all(kwargs):
+ requested_sample_point = (
+ 100
+ * (1 + tseg1) # type: ignore[operator]
+ / (1 + tseg1 + tseg2) # type: ignore[operator]
)
- else:
- self.xldriver.xlCanSetChannelBitrate(
- self.port_handle,
- self.channel_masks[channel],
- bitrate,
+ actual_sample_point = (
+ 100
+ * (1 + bus_params.can.tseg1)
+ / (1 + bus_params.can.tseg1 + bus_params.can.tseg2)
+ )
+ settings_acceptable &= (
+ abs(actual_sample_point - requested_sample_point)
+ < 1.0 # 1 percent threshold
+ )
+
+ if not settings_acceptable:
+ active_settings = ", ".join(
+ [
+ f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name
+ for key, val in bus_params.can._asdict().items()
+ ]
+ )
+ raise CanInitializationError(
+ f"The requested CAN settings could not be set for channel {channel}. "
+ f"Another application might have set incompatible settings. "
+ f"These are the currently active settings: {active_settings}"
)
- LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate)
def _set_bitrate_canfd(
self,
@@ -462,42 +470,112 @@ def _set_bitrate_canfd(
tseg2_dbr: int = 3,
) -> None:
# set parameters if channel has init access
- canfd_conf = xlclass.XLcanFdConf()
- if bitrate:
- canfd_conf.arbitrationBitRate = int(bitrate)
- else:
- canfd_conf.arbitrationBitRate = 500_000
- canfd_conf.sjwAbr = int(sjw_abr)
- canfd_conf.tseg1Abr = int(tseg1_abr)
- canfd_conf.tseg2Abr = int(tseg2_abr)
- if data_bitrate:
- canfd_conf.dataBitRate = int(data_bitrate)
- else:
- canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate)
- canfd_conf.sjwDbr = int(sjw_dbr)
- canfd_conf.tseg1Dbr = int(tseg1_dbr)
- canfd_conf.tseg2Dbr = int(tseg2_dbr)
- self.xldriver.xlCanFdSetConfiguration(
- self.port_handle, self.channel_masks[channel], canfd_conf
- )
- LOG.info(
- "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u",
- canfd_conf.arbitrationBitRate,
- canfd_conf.dataBitRate,
- )
- LOG.info(
- "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
- canfd_conf.sjwAbr,
- canfd_conf.tseg1Abr,
- canfd_conf.tseg2Abr,
+ if self._has_init_access(channel):
+ canfd_conf = xlclass.XLcanFdConf()
+ if bitrate:
+ canfd_conf.arbitrationBitRate = int(bitrate)
+ else:
+ canfd_conf.arbitrationBitRate = 500_000
+ canfd_conf.sjwAbr = int(sjw_abr)
+ canfd_conf.tseg1Abr = int(tseg1_abr)
+ canfd_conf.tseg2Abr = int(tseg2_abr)
+ if data_bitrate:
+ canfd_conf.dataBitRate = int(data_bitrate)
+ else:
+ canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate)
+ canfd_conf.sjwDbr = int(sjw_dbr)
+ canfd_conf.tseg1Dbr = int(tseg1_dbr)
+ canfd_conf.tseg2Dbr = int(tseg2_dbr)
+ self.xldriver.xlCanFdSetConfiguration(
+ self.port_handle, self.channel_masks[channel], canfd_conf
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u",
+ canfd_conf.arbitrationBitRate,
+ canfd_conf.dataBitRate,
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u",
+ canfd_conf.sjwAbr,
+ canfd_conf.tseg1Abr,
+ canfd_conf.tseg2Abr,
+ )
+ LOG.info(
+ "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u",
+ canfd_conf.sjwDbr,
+ canfd_conf.tseg1Dbr,
+ canfd_conf.tseg2Dbr,
+ )
+
+ if self.__testing:
+ return
+
+ # Compare requested CAN settings to active settings
+ bus_params = self._read_bus_params(channel)
+ settings_acceptable = True
+
+ # check bus type
+ settings_acceptable &= (
+ bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN
)
- LOG.info(
- "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u",
- canfd_conf.sjwDbr,
- canfd_conf.tseg1Dbr,
- canfd_conf.tseg2Dbr,
+
+ # check CAN operation mode
+ settings_acceptable &= bool(
+ bus_params.canfd.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
)
+ # check bitrates
+ if bitrate:
+ settings_acceptable &= (
+ abs(bus_params.canfd.bitrate - bitrate) < bitrate / 256
+ )
+ if data_bitrate:
+ settings_acceptable &= (
+ abs(bus_params.canfd.data_bitrate - data_bitrate) < data_bitrate / 256
+ )
+
+ # check sample points
+ if bitrate:
+ requested_nom_sample_point = (
+ 100 * (1 + tseg1_abr) / (1 + tseg1_abr + tseg2_abr)
+ )
+ actual_nom_sample_point = (
+ 100
+ * (1 + bus_params.canfd.tseg1_abr)
+ / (1 + bus_params.canfd.tseg1_abr + bus_params.canfd.tseg2_abr)
+ )
+ settings_acceptable &= (
+ abs(actual_nom_sample_point - requested_nom_sample_point)
+ < 1.0 # 1 percent threshold
+ )
+ if data_bitrate:
+ requested_data_sample_point = (
+ 100 * (1 + tseg1_dbr) / (1 + tseg1_dbr + tseg2_dbr)
+ )
+ actual_data_sample_point = (
+ 100
+ * (1 + bus_params.canfd.tseg1_dbr)
+ / (1 + bus_params.canfd.tseg1_dbr + bus_params.canfd.tseg2_dbr)
+ )
+ settings_acceptable &= (
+ abs(actual_data_sample_point - requested_data_sample_point)
+ < 1.0 # 1 percent threshold
+ )
+
+ if not settings_acceptable:
+ active_settings = ", ".join(
+ [
+ f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name
+ for key, val in bus_params.canfd._asdict().items()
+ ]
+ )
+ raise CanInitializationError(
+ f"The requested CAN FD settings could not be set for channel {channel}. "
+ f"Another application might have set incompatible settings. "
+ f"These are the currently active settings: {active_settings}."
+ )
+
def _apply_filters(self, filters: Optional[CanFilters]) -> None:
if filters:
# Only up to one filter per ID type allowed
diff --git a/test/test_vector.py b/test/test_vector.py
index 21125cc18..02c3c336d 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -459,7 +459,7 @@ def test_reset_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
-def test_reset_mocked() -> None:
+def test_reset() -> None:
bus = canlib.VectorBus(
channel=0, serial=_find_virtual_can_serial(), interface="vector"
)
@@ -696,7 +696,7 @@ def _find_virtual_can_serial() -> int:
for i in range(xl_driver_config.channelCount):
xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i]
- if xl_channel_config.transceiverName.decode() == "Virtual CAN":
+ if "Virtual CAN" in xl_channel_config.transceiverName.decode():
return xl_channel_config.serialNumber
raise LookupError("Vector virtual CAN not found")
From 4713c2ccda821f1fe39238361ed6b30692b70f7d Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 12 Jan 2023 17:56:52 +0100
Subject: [PATCH 220/475] USB2CAN: Faster channel detection on Windows (#1480)
Co-authored-by: zariiii9003
---
can/interfaces/usb2can/serial_selector.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py
index 22a95ae7c..c2e48ff97 100644
--- a/can/interfaces/usb2can/serial_selector.py
+++ b/can/interfaces/usb2can/serial_selector.py
@@ -49,11 +49,13 @@ def find_serial_devices(serial_matcher: str = "") -> List[str]:
:param serial_matcher:
only device IDs starting with this string are returned
"""
- objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
- objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2")
- query = "SELECT * FROM CIM_LogicalDevice where Name LIKE '%USB2CAN%'"
- devices = objSWbemServices.ExecQuery(query)
- serial_numbers = [device.DeviceID.split("\\")[-1] for device in devices]
+ serial_numbers = []
+ wmi = win32com.client.GetObject("winmgmts:")
+ for usb_controller in wmi.InstancesOf("Win32_USBControllerDevice"):
+ usb_device = wmi.Get(usb_controller.Dependent)
+ if "USB2CAN" in usb_device.Name:
+ serial_numbers.append(usb_device.DeviceID.split("\\")[-1])
+
if serial_matcher:
return [sn for sn in serial_numbers if serial_matcher in sn]
return serial_numbers
From ef803a57adac6e74b6a3af81ad52d1a7042814f9 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 12 Jan 2023 18:09:47 +0100
Subject: [PATCH 221/475] Fix logger name
---
can/viewer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/can/viewer.py b/can/viewer.py
index e87684485..5539ee3fb 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -39,7 +39,7 @@
)
-logger = logging.getLogger("can.serial")
+logger = logging.getLogger("can.viewer")
try:
import curses
From 69a5209edb893413d932d8881ad93b5cd599af95 Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Sat, 14 Jan 2023 16:34:29 +0100
Subject: [PATCH 222/475] Enable SocketCAN interface tests in GitHub CI (#1484)
* Update CI to set up vcan and run SocketCAN tests
* Add test to document PyPy raw CAN socket implementation status
* Update test for restarting of SocketCAN SendTask
Previously, it was not permitted to restart an already started period
send task for SocketCAN. This behavior was changed in PR #1440. This
commit adjusts the test to reflect this change.
* Update PyPy raw CAN socket test failure into warning
---
.github/workflows/ci.yml | 10 +++++++++-
test/test_cyclic_socketcan.py | 10 ++--------
test/test_socketcan.py | 35 ++++++++++++++++++++++++++++-------
tox.ini | 1 +
4 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c610c6a76..4f96578e5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,10 +38,18 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install tox
+ - name: Setup SocketCAN
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: |
+ sudo apt-get -y install linux-modules-extra-$(uname -r)
+ sudo ./test/open_vcan.sh
- name: Test with pytest via tox
run: |
tox -e gh
-
+ env:
+ # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets
+ # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809
+ TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.python-version, 'pypy' ) }}"
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py
index ca1db6bfc..30c86d6a5 100644
--- a/test/test_cyclic_socketcan.py
+++ b/test/test_cyclic_socketcan.py
@@ -256,14 +256,8 @@ def test_start_already_started_task(self):
task_a = self._send_bus.send_periodic(messages_a, self.PERIOD)
time.sleep(0.1)
- # Try to start it again, task_id is not incremented in this case
- with self.assertRaises(can.CanOperationError) as ctx:
- task_a.start()
- self.assertEqual(
- "A periodic task for task ID 1 is already in progress by the SocketCAN Linux layer",
- str(ctx.exception),
- )
-
+ # Task restarting is permitted as of #1440
+ task_a.start()
task_a.stop()
def test_create_same_id(self):
diff --git a/test/test_socketcan.py b/test/test_socketcan.py
index 1c38e1583..324890dad 100644
--- a/test/test_socketcan.py
+++ b/test/test_socketcan.py
@@ -6,8 +6,17 @@
import ctypes
import struct
import unittest
+import warnings
from unittest.mock import patch
+import can
+from can.interfaces.socketcan.constants import (
+ CAN_BCM_TX_DELETE,
+ CAN_BCM_TX_SETUP,
+ SETTIMER,
+ STARTTIMER,
+ TX_COUNTEVT,
+)
from can.interfaces.socketcan.socketcan import (
bcm_header_factory,
build_bcm_header,
@@ -16,13 +25,7 @@
build_bcm_update_header,
BcmMsgHead,
)
-from can.interfaces.socketcan.constants import (
- CAN_BCM_TX_DELETE,
- CAN_BCM_TX_SETUP,
- SETTIMER,
- STARTTIMER,
- TX_COUNTEVT,
-)
+from .config import IS_LINUX, IS_PYPY
class SocketCANTest(unittest.TestCase):
@@ -353,6 +356,24 @@ def test_build_bcm_update_header(self):
self.assertEqual(can_id, result.can_id)
self.assertEqual(1, result.nframes)
+ @unittest.skipUnless(IS_LINUX and IS_PYPY, "Only test when run on Linux with PyPy")
+ def test_pypy_socketcan_support(self):
+ """Wait for PyPy raw CAN socket support
+
+ This test shall document raw CAN socket support under PyPy. Once this test fails, it is likely that PyPy
+ either implemented raw CAN socket support or at least changed the error that is thrown.
+ https://foss.heptapod.net/pypy/pypy/-/issues/3809
+ https://github.com/hardbyte/python-can/issues/1479
+ """
+ try:
+ can.Bus(interface="socketcan", channel="vcan0", bitrate=500000)
+ except OSError as e:
+ if "unknown address family" not in str(e):
+ warnings.warn(
+ "Please check if PyPy has implemented raw CAN socket support! "
+ "See: https://foss.heptapod.net/pypy/pypy/-/issues/3809"
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/tox.ini b/tox.ini
index 0dbd6423e..96cc82425 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,6 +26,7 @@ passenv =
GITHUB_*
COVERALLS_*
PY_COLORS
+ TEST_SOCKETCAN
[testenv:travis]
passenv =
From 35de98eccfa84f86f4754abec58d52037b4e8ffa Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 20 Jan 2023 15:50:33 +0100
Subject: [PATCH 223/475] Update PCAN Basic to 4.6.2.753 (#1481)
* update PCAN Basic to 4.6.2.753
* faster channel detection
* fix PcanBus._detect_available_configs
* remove TODO
* add more information to pcan channel detection
Co-authored-by: zariiii9003
---
can/interfaces/pcan/basic.py | 82 ++++++++++++++++++++++++------------
can/interfaces/pcan/pcan.py | 50 ++++++++++++++++++----
test/test_pcan.py | 30 ++++++++++---
3 files changed, 120 insertions(+), 42 deletions(-)
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index e9fc5029a..77be2c854 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -8,15 +8,15 @@
#
# ------------------------------------------------------------------
# Author : Keneth Wagner
+# Last change: 2022-07-06
# ------------------------------------------------------------------
#
-# Copyright (C) 1999-2021 PEAK-System Technik GmbH, Darmstadt
+# Copyright (C) 1999-2022 PEAK-System Technik GmbH, Darmstadt
# more Info at http://www.peak-system.com
# Module Imports
from ctypes import *
from ctypes.util import find_library
-from string import *
import platform
import logging
@@ -282,6 +282,12 @@
PCAN_ATTACHED_CHANNELS = TPCANParameter(
0x2B
) # Get information about PCAN channels attached to a system
+PCAN_ALLOW_ECHO_FRAMES = TPCANParameter(
+ 0x2C
+) # Echo messages reception status within a PCAN-Channel
+PCAN_DEVICE_PART_NUMBER = TPCANParameter(
+ 0x2D
+) # Get the part number associated to a device
# DEPRECATED parameters
#
@@ -354,8 +360,8 @@
33
) # Maximum length of the name of a device: 32 characters + terminator
MAX_LENGTH_VERSION_STRING = int(
- 18
-) # Maximum length of a version string: 17 characters + terminator
+ 256
+) # Maximum length of a version string: 255 characters + terminator
# PCAN message types
#
@@ -377,6 +383,9 @@
PCAN_MESSAGE_ESI = TPCANMessageType(
0x10
) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active)
+PCAN_MESSAGE_ECHO = TPCANMessageType(
+ 0x20
+) # The PCAN message represents an echo CAN Frame
PCAN_MESSAGE_ERRFRAME = TPCANMessageType(
0x40
) # The PCAN message represents an error frame
@@ -654,33 +663,38 @@ class PCANBasic:
"""PCAN-Basic API class implementation"""
def __init__(self):
- # Loads the PCANBasic API
- #
if platform.system() == "Windows":
- # Loads the API on Windows
- _dll_path = find_library("PCANBasic")
- self.__m_dllBasic = windll.LoadLibrary(_dll_path) if _dll_path else None
- aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
- try:
- aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers")
- winreg.CloseKey(aKey)
- except OSError:
- logger.error("Exception: The PEAK-driver couldn't be found!")
- finally:
- winreg.CloseKey(aReg)
- elif "CYGWIN" in platform.system():
- self.__m_dllBasic = cdll.LoadLibrary("PCANBasic.dll")
- # Unfortunately cygwin python has no winreg module, so we can't
- # check for the registry key.
- elif platform.system() == "Linux":
- # Loads the API on Linux
- self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so")
+ load_library_func = windll.LoadLibrary
+
+ # look for Peak drivers in Windows registry
+ with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as reg:
+ try:
+ with winreg.OpenKey(reg, r"SOFTWARE\PEAK-System\PEAK-Drivers"):
+ pass
+ except OSError:
+ raise OSError("The PEAK-driver could not be found!") from None
+ else:
+ load_library_func = cdll.LoadLibrary
+
+ if platform.system() == "Windows" or "CYGWIN" in platform.system():
+ lib_name = "PCANBasic.dll"
elif platform.system() == "Darwin":
- self.__m_dllBasic = cdll.LoadLibrary(find_library("libPCBUSB.dylib"))
+ # PCBUSB library is a third-party software created
+ # and maintained by the MacCAN project
+ lib_name = "libPCBUSB.dylib"
else:
- self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so")
- if self.__m_dllBasic is None:
- logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!")
+ lib_name = "libpcanbasic.so"
+
+ lib_path = find_library(lib_name)
+ if not lib_path:
+ raise OSError(f"{lib_name} library not found.")
+
+ try:
+ self.__m_dllBasic = load_library_func(lib_path)
+ except OSError:
+ raise OSError(
+ f"The PCAN-Basic API could not be loaded. ({lib_path})"
+ ) from None
# Initializes a PCAN Channel
#
@@ -965,6 +979,7 @@ def GetValue(self, Channel, Parameter):
or Parameter == PCAN_BITRATE_INFO_FD
or Parameter == PCAN_IP_ADDRESS
or Parameter == PCAN_FIRMWARE_VERSION
+ or Parameter == PCAN_DEVICE_PART_NUMBER
):
mybuffer = create_string_buffer(256)
@@ -974,6 +989,12 @@ def GetValue(self, Channel, Parameter):
return (TPCANStatus(res[0]),)
mybuffer = (TPCANChannelInformation * res[1])()
+ elif (
+ Parameter == PCAN_ACCEPTANCE_FILTER_11BIT
+ or PCAN_ACCEPTANCE_FILTER_29BIT
+ ):
+ mybuffer = c_int64(0)
+
else:
mybuffer = c_int(0)
@@ -1017,6 +1038,11 @@ def SetValue(self, Channel, Parameter, Buffer):
or Parameter == PCAN_TRACE_LOCATION
):
mybuffer = create_string_buffer(256)
+ elif (
+ Parameter == PCAN_ACCEPTANCE_FILTER_11BIT
+ or PCAN_ACCEPTANCE_FILTER_29BIT
+ ):
+ mybuffer = c_int64(0)
else:
mybuffer = c_int(0)
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index cd0349c99..7f9b31f2f 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -6,16 +6,19 @@
import time
from datetime import datetime
import platform
-
-from typing import Optional
+from typing import Optional, List
from packaging import version
-from ...message import Message
-from ...bus import BusABC, BusState
-from ...util import len2dlc, dlc2len
-from ...exceptions import CanError, CanOperationError, CanInitializationError
-
+from can import (
+ BusABC,
+ BusState,
+ CanError,
+ CanOperationError,
+ CanInitializationError,
+ Message,
+)
+from can.util import len2dlc, dlc2len
from .basic import (
PCAN_BITRATES,
@@ -57,6 +60,8 @@
FEATURE_FD_CAPABLE,
PCAN_DICT_STATUS,
PCAN_BUSOFF_AUTORESET,
+ PCAN_ATTACHED_CHANNELS,
+ TPCANChannelInformation,
)
@@ -78,7 +83,6 @@
except ImportError as error:
log.warning(
"uptime library not available, timestamps are relative to boot time and not to Epoch UTC",
- exc_info=True,
)
boottimeEpoch = 0
@@ -278,7 +282,7 @@ def __init__(
# TODO Remove Filter when MACCan actually supports it:
# https://github.com/mac-can/PCBUSB-Library/
log.debug(
- "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.10"
+ "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.11.2"
)
if kwargs.get("auto_reset", False):
@@ -624,7 +628,35 @@ def _detect_available_configs():
library_handle = PCANBasic()
except OSError:
return channels
+
interfaces = []
+
+ if platform.system() != "Darwin":
+ res, value = library_handle.GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS)
+ if res != PCAN_ERROR_OK:
+ return interfaces
+ channel_information: List[TPCANChannelInformation] = list(value)
+ for channel in channel_information:
+ # find channel name in PCAN_CHANNEL_NAMES by value
+ channel_name = next(
+ _channel_name
+ for _channel_name, channel_id in PCAN_CHANNEL_NAMES.items()
+ if channel_id.value == channel.channel_handle
+ )
+ channel_config = {
+ "interface": "pcan",
+ "channel": channel_name,
+ "supports_fd": bool(channel.device_features & FEATURE_FD_CAPABLE),
+ "controller_number": channel.controller_number,
+ "device_features": channel.device_features,
+ "device_id": channel.device_id,
+ "device_name": channel.device_name.decode("latin-1"),
+ "device_type": channel.device_type,
+ "channel_condition": channel.channel_condition,
+ }
+ interfaces.append(channel_config)
+ return interfaces
+
for i in range(16):
interfaces.append(
{
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 7e8e27cf6..10a69d7b9 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -3,6 +3,7 @@
"""
import ctypes
+import platform
import unittest
from unittest import mock
from unittest.mock import Mock
@@ -330,11 +331,30 @@ def test_state(self, name, bus_state: BusState, expected_parameter) -> None:
)
def test_detect_available_configs(self) -> None:
- self.mock_pcan.GetValue = Mock(
- return_value=(PCAN_ERROR_OK, PCAN_CHANNEL_AVAILABLE)
- )
- configs = PcanBus._detect_available_configs()
- self.assertEqual(len(configs), 50)
+ if platform.system() == "Darwin":
+ self.mock_pcan.GetValue = Mock(
+ return_value=(PCAN_ERROR_OK, PCAN_CHANNEL_AVAILABLE)
+ )
+ configs = PcanBus._detect_available_configs()
+ self.assertEqual(len(configs), 50)
+ else:
+ value = (TPCANChannelInformation * 1).from_buffer_copy(
+ b"Q\x00\x05\x00\x01\x00\x00\x00PCAN-USB FD\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b'\x00\x00\x00\x00\x00\x00\x003"\x11\x00\x01\x00\x00\x00'
+ )
+ self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_OK, value))
+ configs = PcanBus._detect_available_configs()
+ assert len(configs) == 1
+ assert configs[0]["interface"] == "pcan"
+ assert configs[0]["channel"] == "PCAN_USBBUS1"
+ assert configs[0]["supports_fd"]
+ assert configs[0]["controller_number"] == 0
+ assert configs[0]["device_features"] == 1
+ assert configs[0]["device_id"] == 1122867
+ assert configs[0]["device_name"] == "PCAN-USB FD"
+ assert configs[0]["device_type"] == 5
+ assert configs[0]["channel_condition"] == 1
@parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)])
def test_status_string(self, name, status, expected_result) -> None:
From 5c523ec9cc5ab3badbb6def6fb3750d228c7c7c0 Mon Sep 17 00:00:00 2001
From: BKaDamien <94377088+BKaDamien@users.noreply.github.com>
Date: Mon, 23 Jan 2023 09:26:13 +0100
Subject: [PATCH 224/475] Add usage of select instead of polling for Linux
based platform (PCAN Interface) (#1410)
* - integrate handling of Linux based event using select for pcan interface
* - adapt pcan unit tests regarding PCAN_RECEIVE_EVENT GetValue call
* - add the case HAS_EVENTS = FALSE whne no special event basic mechanism is importes
* - force select mock for test_recv_no_message
* - just for testing : see what is going wrong with CI testing and my setup
* - correct patch on pcan test test_recv_no_message
- run black
* - remove debug log
* -just reformat test_pcan
* - move global variable IS_WINDOW and IS_LINUX from pcan to basic module
* - remove redundant comment in pcan _recv_internal
* refactor _recv_internal
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/interfaces/pcan/basic.py | 7 +-
can/interfaces/pcan/pcan.py | 178 ++++++++++++++++++++---------------
test/test_pcan.py | 9 +-
3 files changed, 113 insertions(+), 81 deletions(-)
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index 77be2c854..5f161eecc 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -21,9 +21,12 @@
import logging
-if platform.system() == "Windows":
- import winreg
+PLATFORM = platform.system()
+IS_WINDOWS = PLATFORM == "Windows"
+IS_LINUX = PLATFORM == "Linux"
+if IS_WINDOWS:
+ import winreg
logger = logging.getLogger("can.pcan")
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 7f9b31f2f..01adbe0c2 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -1,12 +1,11 @@
"""
Enable basic CAN over a PCAN USB device.
"""
-
import logging
import time
from datetime import datetime
import platform
-from typing import Optional, List
+from typing import Optional, List, Tuple
from packaging import version
@@ -50,6 +49,8 @@
PCAN_LISTEN_ONLY,
PCAN_PARAMETER_OFF,
TPCANHandle,
+ IS_LINUX,
+ IS_WINDOWS,
PCAN_PCIBUS1,
PCAN_USBBUS1,
PCAN_PCCBUS1,
@@ -70,7 +71,6 @@
MIN_PCAN_API_VERSION = version.parse("4.2.0")
-
try:
# use the "uptime" library if available
import uptime
@@ -86,22 +86,27 @@
)
boottimeEpoch = 0
-try:
- # Try builtin Python 3 Windows API
- from _overlapped import CreateEvent
- from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
+HAS_EVENTS = False
- HAS_EVENTS = True
-except ImportError:
+if IS_WINDOWS:
try:
- # Try pywin32 package
- from win32event import CreateEvent
- from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
+ # Try builtin Python 3 Windows API
+ from _overlapped import CreateEvent
+ from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
HAS_EVENTS = True
except ImportError:
- # Use polling instead
- HAS_EVENTS = False
+ pass
+
+elif IS_LINUX:
+ try:
+ import errno
+ import os
+ import select
+
+ HAS_EVENTS = True
+ except Exception:
+ pass
class PcanBus(BusABC):
@@ -294,10 +299,16 @@ def __init__(
raise PcanCanInitializationError(self._get_formatted_error(result))
if HAS_EVENTS:
- self._recv_event = CreateEvent(None, 0, 0, None)
- result = self.m_objPCANBasic.SetValue(
- self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event
- )
+ if IS_WINDOWS:
+ self._recv_event = CreateEvent(None, 0, 0, None)
+ result = self.m_objPCANBasic.SetValue(
+ self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event
+ )
+ elif IS_LINUX:
+ result, self._recv_event = self.m_objPCANBasic.GetValue(
+ self.m_PcanHandle, PCAN_RECEIVE_EVENT
+ )
+
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
@@ -441,84 +452,96 @@ def set_device_number(self, device_number):
return False
return True
- def _recv_internal(self, timeout):
+ def _recv_internal(
+ self, timeout: Optional[float]
+ ) -> Tuple[Optional[Message], bool]:
+ end_time = time.time() + timeout if timeout is not None else None
- if HAS_EVENTS:
- # We will utilize events for the timeout handling
- timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE
- elif timeout is not None:
- # Calculate max time
- end_time = time.perf_counter() + timeout
-
- # log.debug("Trying to read a msg")
-
- result = None
- while result is None:
+ while True:
if self.fd:
- result = self.m_objPCANBasic.ReadFD(self.m_PcanHandle)
+ result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD(
+ self.m_PcanHandle
+ )
else:
- result = self.m_objPCANBasic.Read(self.m_PcanHandle)
- if result[0] == PCAN_ERROR_QRCVEMPTY:
- if HAS_EVENTS:
- result = None
- val = WaitForSingleObject(self._recv_event, timeout_ms)
- if val != WAIT_OBJECT_0:
- return None, False
- elif timeout is not None and time.perf_counter() >= end_time:
- return None, False
+ result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.Read(
+ self.m_PcanHandle
+ )
+
+ if result == PCAN_ERROR_OK:
+ # message received
+ break
+
+ if result == PCAN_ERROR_QRCVEMPTY:
+ # receive queue is empty, wait or return on timeout
+
+ if end_time is None:
+ time_left: Optional[float] = None
+ timed_out = False
else:
- result = None
+ time_left = max(0.0, end_time - time.time())
+ timed_out = time_left == 0.0
+
+ if timed_out:
+ return None, False
+
+ if not HAS_EVENTS:
+ # polling mode
time.sleep(0.001)
- elif result[0] & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY):
- log.warning(self._get_formatted_error(result[0]))
- return None, False
- elif result[0] != PCAN_ERROR_OK:
- raise PcanCanOperationError(self._get_formatted_error(result[0]))
-
- theMsg = result[1]
- itsTimeStamp = result[2]
-
- # log.debug("Received a message")
-
- is_extended_id = (
- theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value
- ) == PCAN_MESSAGE_EXTENDED.value
- is_remote_frame = (
- theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value
- ) == PCAN_MESSAGE_RTR.value
- is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value
- bitrate_switch = (
- theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value
- ) == PCAN_MESSAGE_BRS.value
- error_state_indicator = (
- theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value
- ) == PCAN_MESSAGE_ESI.value
- is_error_frame = (
- theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value
- ) == PCAN_MESSAGE_ERRFRAME.value
+ continue
+
+ if IS_WINDOWS:
+ # Windows with event
+ if time_left is None:
+ time_left_ms = INFINITE
+ else:
+ time_left_ms = int(time_left * 1000)
+ _ret = WaitForSingleObject(self._recv_event, time_left_ms)
+ if _ret == WAIT_OBJECT_0:
+ continue
+
+ elif IS_LINUX:
+ # Linux with event
+ recv, _, _ = select.select([self._recv_event], [], [], time_left)
+ if self._recv_event in recv:
+ continue
+
+ elif result & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY):
+ log.warning(self._get_formatted_error(result))
+
+ else:
+ raise PcanCanOperationError(self._get_formatted_error(result))
+
+ return None, False
+
+ is_extended_id = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value)
+ is_remote_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_RTR.value)
+ is_fd = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_FD.value)
+ bitrate_switch = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_BRS.value)
+ error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value)
+ is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value)
if self.fd:
- dlc = dlc2len(theMsg.DLC)
- timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0))
+ dlc = dlc2len(pcan_msg.DLC)
+ timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0))
else:
- dlc = theMsg.LEN
+ dlc = pcan_msg.LEN
timestamp = boottimeEpoch + (
(
- itsTimeStamp.micros
- + 1000 * itsTimeStamp.millis
- + 0x100000000 * 1000 * itsTimeStamp.millis_overflow
+ pcan_timestamp.micros
+ + 1000 * pcan_timestamp.millis
+ + 0x100000000 * 1000 * pcan_timestamp.millis_overflow
)
/ (1000.0 * 1000.0)
)
rx_msg = Message(
timestamp=timestamp,
- arbitration_id=theMsg.ID,
+ arbitration_id=pcan_msg.ID,
is_extended_id=is_extended_id,
is_remote_frame=is_remote_frame,
is_error_frame=is_error_frame,
dlc=dlc,
- data=theMsg.DATA[:dlc],
+ data=pcan_msg.DATA[:dlc],
is_fd=is_fd,
bitrate_switch=bitrate_switch,
error_state_indicator=error_state_indicator,
@@ -597,6 +620,9 @@ def flash(self, flash):
def shutdown(self):
super().shutdown()
+ if HAS_EVENTS and IS_LINUX:
+ self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_RECEIVE_EVENT, 0)
+
self.m_objPCANBasic.Uninitialize(self.m_PcanHandle)
@property
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 10a69d7b9..01dac848c 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -6,7 +6,8 @@
import platform
import unittest
from unittest import mock
-from unittest.mock import Mock
+from unittest.mock import Mock, patch
+
import pytest
from parameterized import parameterized
@@ -30,7 +31,6 @@ def setUp(self) -> None:
self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK)
self.mock_pcan.GetValue = self._mockGetValue
self.PCAN_API_VERSION_SIM = "4.2"
-
self.bus = None
def tearDown(self) -> None:
@@ -45,6 +45,8 @@ def _mockGetValue(self, channel, parameter):
"""
if parameter == PCAN_API_VERSION:
return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
+ elif parameter == PCAN_RECEIVE_EVENT:
+ return PCAN_ERROR_OK, int.from_bytes(PCAN_RECEIVE_EVENT, "big")
raise NotImplementedError(
f"No mock return value specified for parameter {parameter}"
)
@@ -205,7 +207,8 @@ def test_recv_fd(self):
self.assertEqual(recv_msg.timestamp, 0)
@pytest.mark.timeout(3.0)
- def test_recv_no_message(self):
+ @patch("select.select", return_value=([], [], []))
+ def test_recv_no_message(self, mock_select):
self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None))
self.bus = can.Bus(interface="pcan")
self.assertEqual(self.bus.recv(timeout=0.5), None)
From 3c7520e91d70ea2b825ddc58102ad9eec122be74 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Tue, 24 Jan 2023 17:24:24 +0100
Subject: [PATCH 225/475] Add ODB II url to docs (#1497)
---
doc/index.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/index.rst b/doc/index.rst
index c55108d97..402a485e7 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -14,7 +14,7 @@ linux such as a BeagleBone or RaspberryPi.
More concretely, some example uses of the library:
* Passively logging what occurs on a CAN bus. For example monitoring a
- commercial vehicle using its **OBD-II** port.
+ commercial vehicle using its `OBD-II port `__.
* Testing of hardware that interacts via CAN. Modules found in
modern cars, motorcycles, boats, and even wheelchairs have had components tested
From 48b18f1b8f4836e657407df1761ea9d0e3efd18b Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 25 Jan 2023 22:16:48 +0100
Subject: [PATCH 226/475] Unify bit timings with separate class for CAN FD
(#1468)
* rework bit timings
* fix pylint
* convert config values to int
* simplify instantiation from sample point
* add docstrings
* update bit timing docs and tests
* fix wrong info
* remove links
* adapt CANalystIIBus
* adapt CantactBus
* try to set correct f_clock before raising exception
* improve bit timing docs
* turn `oscillator_tolerance` into function, improve `from_sample_point`
* add tests
* cleanup
* Remove BitTimingFd
Co-authored-by: Lukas Magel
* Update can/interfaces/cantact.py
Co-authored-by: Lukas Magel
* Add test_btr_persistence suggestion
Co-authored-by: Lukas Magel
* Format code with black
* move imports up
* undo type hint change
* mention dict conversion in docs
* change member order for docs
* use bitrate prescaler as ground truth
* avoid repetition
* Add utility method to validate and adjust the timing clock value
* Fix ZeroDivisionError
* improve tests and docs
* add deprecation period
* check if "timing" already exists
* add bit timing figure to docs
Co-authored-by: zariiii9003
Co-authored-by: Lukas Magel
Co-authored-by: Lukas Magel
---
can/__init__.py | 2 +-
can/bit_timing.py | 1136 +++++++++++++++++++++++++----
can/interfaces/canalystii.py | 45 +-
can/interfaces/cantact.py | 58 +-
can/typechecking.py | 21 +
can/util.py | 86 ++-
doc/bit_timing.rst | 113 ++-
doc/conf.py | 2 +
doc/images/bit_timing_dark.svg | 96 +++
doc/images/bit_timing_light.svg | 92 +++
test/test_bit_timing.py | 397 +++++++++-
test/test_cantact.py | 4 +-
test/test_interface_canalystii.py | 14 +-
test/test_util.py | 95 +++
14 files changed, 1901 insertions(+), 260 deletions(-)
create mode 100644 doc/images/bit_timing_dark.svg
create mode 100644 doc/images/bit_timing_light.svg
diff --git a/can/__init__.py b/can/__init__.py
index 773e94022..d9ff5ffcd 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -33,7 +33,7 @@
from .interfaces import VALID_INTERFACES
from . import interface
from .interface import Bus, detect_available_configs
-from .bit_timing import BitTiming
+from .bit_timing import BitTiming, BitTimingFd
from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync
from .io import ASCWriter, ASCReader
diff --git a/can/bit_timing.py b/can/bit_timing.py
index b0ad762fb..2dc78064b 100644
--- a/can/bit_timing.py
+++ b/can/bit_timing.py
@@ -1,50 +1,125 @@
-from typing import Optional, Union
+# pylint: disable=too-many-lines
+import math
+from typing import List, Mapping, Iterator, cast
+from can.typechecking import BitTimingFdDict, BitTimingDict
-class BitTiming:
- """Representation of a bit timing configuration.
- The class can be constructed in various ways, depending on the information
- available or the capabilities of the interfaces that need to be supported.
+class BitTiming(Mapping):
+ """Representation of a bit timing configuration for a CAN 2.0 bus.
- The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW::
+ The class can be constructed in multiple ways, depending on the information
+ available. The preferred way is using CAN clock frequency, prescaler, tseg1, tseg2 and sjw::
- can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1)
+ can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=1, sjw=1)
- If the clock frequency is unknown it may be omitted but some interfaces may
- require it.
+ Alternatively you can set the bitrate instead of the bit rate prescaler::
- Alternatively the BRP can be given instead of bitrate and clock frequency but this
- will limit the number of supported interfaces.
+ can.BitTiming.from_bitrate_and_segments(
+ f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=1, sjw=1
+ )
- It is also possible specify BTR registers directly,
- but will not work for all interfaces::
+ It is also possible to specify BTR registers::
- can.BitTiming(btr0=0x00, btr1=0x14)
- """
+ can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14)
+
+ or to calculate the timings for a given sample point::
- sync_seg = 1
+ can.BitTiming.from_sample_point(f_clock=8_000_000, bitrate=1_000_000, sample_point=75.0)
+ """
def __init__(
self,
- bitrate: Optional[int] = None,
- f_clock: Optional[int] = None,
- brp: Optional[int] = None,
- tseg1: Optional[int] = None,
- tseg2: Optional[int] = None,
- sjw: Optional[int] = None,
+ f_clock: int,
+ brp: int,
+ tseg1: int,
+ tseg2: int,
+ sjw: int,
nof_samples: int = 1,
- btr0: Optional[int] = None,
- btr1: Optional[int] = None,
- ):
+ ) -> None:
"""
- :param int bitrate:
- Bitrate in bits/s.
:param int f_clock:
The CAN system clock frequency in Hz.
- Usually the oscillator frequency divided by 2.
:param int brp:
- Bit Rate Prescaler. Prefer to use bitrate and f_clock instead.
+ Bit rate prescaler.
+ :param int tseg1:
+ Time segment 1, that is, the number of quanta from (but not including)
+ the Sync Segment to the sampling point.
+ :param int tseg2:
+ Time segment 2, that is, the number of quanta from the sampling
+ point to the end of the bit.
+ :param int sjw:
+ The Synchronization Jump Width. Decides the maximum number of time quanta
+ that the controller can resynchronize every bit.
+ :param int nof_samples:
+ 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,
+ with the last sample being taken in the edge between TSEG1 and TSEG2.
+ Three samples should only be used for relatively slow baudrates.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
+ self._data: BitTimingDict = {
+ "f_clock": f_clock,
+ "brp": brp,
+ "tseg1": tseg1,
+ "tseg2": tseg2,
+ "sjw": sjw,
+ "nof_samples": nof_samples,
+ }
+ self._validate()
+
+ def _validate(self) -> None:
+ if not 8 <= self.nbt <= 25:
+ raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].")
+
+ if not 1 <= self.brp <= 64:
+ raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].")
+
+ if not 5_000 <= self.bitrate <= 2_000_000:
+ raise ValueError(
+ f"bitrate (={self.bitrate}) must be in [5,000...2,000,000]."
+ )
+
+ if not 1 <= self.tseg1 <= 16:
+ raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].")
+
+ if not 1 <= self.tseg2 <= 8:
+ raise ValueError(f"tseg2 (={self.tseg2}) must be in [1...8].")
+
+ if not 1 <= self.sjw <= 4:
+ raise ValueError(f"sjw (={self.sjw}) must be in [1...4].")
+
+ if self.sjw > self.tseg2:
+ raise ValueError(
+ f"sjw (={self.sjw}) must not be greater than tseg2 (={self.tseg2})."
+ )
+
+ if self.sample_point < 50.0:
+ raise ValueError(
+ f"The sample point must be greater than or equal to 50% "
+ f"(sample_point={self.sample_point:.2f}%)."
+ )
+
+ if self.nof_samples not in (1, 3):
+ raise ValueError("nof_samples must be 1 or 3")
+
+ @classmethod
+ def from_bitrate_and_segments(
+ cls,
+ f_clock: int,
+ bitrate: int,
+ tseg1: int,
+ tseg2: int,
+ sjw: int,
+ nof_samples: int = 1,
+ ) -> "BitTiming":
+ """Create a :class:`~can.BitTiming` instance from bitrate and segment lengths.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :param int bitrate:
+ Bitrate in bit/s.
:param int tseg1:
Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
@@ -59,59 +134,154 @@ def __init__(
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.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
+ try:
+ brp = int(round(f_clock / (bitrate * (1 + tseg1 + tseg2))))
+ except ZeroDivisionError:
+ raise ValueError("Invalid inputs") from None
+
+ bt = cls(
+ f_clock=f_clock,
+ brp=brp,
+ tseg1=tseg1,
+ tseg2=tseg2,
+ sjw=sjw,
+ nof_samples=nof_samples,
+ )
+ if abs(bt.bitrate - bitrate) > bitrate / 256:
+ raise ValueError(
+ f"the effective bitrate (={bt.bitrate}) diverges "
+ f"from the requested bitrate (={bitrate})"
+ )
+ return bt
+
+ @classmethod
+ def from_registers(
+ cls,
+ f_clock: int,
+ btr0: int,
+ btr1: int,
+ ) -> "BitTiming":
+ """Create a :class:`~can.BitTiming` instance from registers btr0 and btr1.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
:param int btr0:
The BTR0 register value used by many CAN controllers.
:param int btr1:
The BTR1 register value used by many CAN controllers.
+ :raises ValueError:
+ if the arguments are invalid.
"""
- self._bitrate = bitrate
- self._brp = brp
- self._sjw = sjw
- self._tseg1 = tseg1
- self._tseg2 = tseg2
- self._nof_samples = nof_samples
- self._f_clock = f_clock
-
- if btr0 is not None:
- self._brp = (btr0 & 0x3F) + 1
- self._sjw = (btr0 >> 6) + 1
- if btr1 is not None:
- self._tseg1 = (btr1 & 0xF) + 1
- self._tseg2 = ((btr1 >> 4) & 0x7) + 1
- self._nof_samples = 3 if btr1 & 0x80 else 1
-
- if nof_samples not in (1, 3):
- raise ValueError("nof_samples must be 1 or 3")
+ brp = (btr0 & 0x3F) + 1
+ sjw = (btr0 >> 6) + 1
+ tseg1 = (btr1 & 0xF) + 1
+ tseg2 = ((btr1 >> 4) & 0x7) + 1
+ nof_samples = 3 if btr1 & 0x80 else 1
+ return cls(
+ brp=brp,
+ f_clock=f_clock,
+ tseg1=tseg1,
+ tseg2=tseg2,
+ sjw=sjw,
+ nof_samples=nof_samples,
+ )
+
+ @classmethod
+ def from_sample_point(
+ cls, f_clock: int, bitrate: int, sample_point: float = 69.0
+ ) -> "BitTiming":
+ """Create a :class:`~can.BitTiming` instance for a sample point.
+
+ This function tries to find bit timings, which are close to the requested
+ sample point. It does not take physical bus properties into account, so the
+ calculated bus timings might not work properly for you.
+
+ The :func:`oscillator_tolerance` function might be helpful to evaluate the
+ bus timings.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :param int bitrate:
+ Bitrate in bit/s.
+ :param int sample_point:
+ The sample point value in percent.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
+
+ if sample_point < 50.0:
+ raise ValueError(f"sample_point (={sample_point}) must not be below 50%.")
+
+ possible_solutions: List[BitTiming] = []
+ for brp in range(1, 65):
+ nbt = round(int(f_clock / (bitrate * brp)))
+ if nbt < 8:
+ break
+
+ effective_bitrate = f_clock / (nbt * brp)
+ if abs(effective_bitrate - bitrate) > bitrate / 256:
+ continue
+
+ tseg1 = int(round(sample_point / 100 * nbt)) - 1
+ # limit tseg1, so tseg2 is at least 1 TQ
+ tseg1 = min(tseg1, nbt - 2)
+
+ tseg2 = nbt - tseg1 - 1
+ sjw = min(tseg2, 4)
+
+ try:
+ bt = BitTiming(
+ f_clock=f_clock,
+ brp=brp,
+ tseg1=tseg1,
+ tseg2=tseg2,
+ sjw=sjw,
+ )
+ possible_solutions.append(bt)
+ except ValueError:
+ continue
+
+ if not possible_solutions:
+ raise ValueError("No suitable bit timings found.")
+
+ # sort solutions
+ for key, reverse in (
+ # prefer low prescaler
+ (lambda x: x.brp, False),
+ # prefer low sample point deviation from requested values
+ (lambda x: abs(x.sample_point - sample_point), False),
+ ):
+ possible_solutions.sort(key=key, reverse=reverse)
+
+ return possible_solutions[0]
@property
- def nbt(self) -> int:
- """Nominal Bit Time."""
- return self.sync_seg + self.tseg1 + self.tseg2
+ def f_clock(self) -> int:
+ """The CAN system clock frequency in Hz."""
+ return self._data["f_clock"]
@property
- def bitrate(self) -> Union[int, float]:
+ def bitrate(self) -> int:
"""Bitrate in bits/s."""
- if self._bitrate:
- return self._bitrate
- if self._f_clock and self._brp:
- return self._f_clock / (self._brp * self.nbt)
- raise ValueError("bitrate must be specified")
+ return int(round(self.f_clock / (self.nbt * self.brp)))
@property
def brp(self) -> int:
"""Bit Rate Prescaler."""
- if self._brp:
- return self._brp
- if self._f_clock and self._bitrate:
- return round(self._f_clock / (self._bitrate * self.nbt))
- raise ValueError("Either bitrate and f_clock or brp must be specified")
+ return self._data["brp"]
@property
- def sjw(self) -> int:
- """Synchronization Jump Width."""
- if not self._sjw:
- raise ValueError("sjw must be specified")
- return self._sjw
+ def tq(self) -> int:
+ """Time quantum in nanoseconds"""
+ return int(round(self.brp / self.f_clock * 1e9))
+
+ @property
+ def nbt(self) -> int:
+ """Nominal Bit Time."""
+ return 1 + self.tseg1 + self.tseg2
@property
def tseg1(self) -> int:
@@ -119,9 +289,7 @@ def tseg1(self) -> int:
The number of quanta from (but not including) the Sync Segment to the sampling point.
"""
- if not self._tseg1:
- raise ValueError("tseg1 must be specified")
- return self._tseg1
+ return self._data["tseg1"]
@property
def tseg2(self) -> int:
@@ -129,104 +297,788 @@ def tseg2(self) -> int:
The number of quanta from the sampling point to the end of the bit.
"""
- if not self._tseg2:
- raise ValueError("tseg2 must be specified")
- return self._tseg2
+ return self._data["tseg2"]
@property
- def nof_samples(self) -> int:
- """Number of samples (1 or 3)."""
- if not self._nof_samples:
- raise ValueError("nof_samples must be specified")
- return self._nof_samples
+ def sjw(self) -> int:
+ """Synchronization Jump Width."""
+ return self._data["sjw"]
@property
- def f_clock(self) -> int:
- """The CAN system clock frequency in Hz.
-
- Usually the oscillator frequency divided by 2.
- """
- if not self._f_clock:
- raise ValueError("f_clock must be specified")
- return self._f_clock
+ def nof_samples(self) -> int:
+ """Number of samples (1 or 3)."""
+ return self._data["nof_samples"]
@property
def sample_point(self) -> float:
"""Sample point in percent."""
- return 100.0 * (self.nbt - self.tseg2) / self.nbt
+ return 100.0 * (1 + self.tseg1) / (1 + self.tseg1 + self.tseg2)
@property
def btr0(self) -> int:
- sjw = self.sjw
- brp = self.brp
-
- if brp < 1 or brp > 64:
- raise ValueError("brp must be 1 - 64")
- if sjw < 1 or sjw > 4:
- raise ValueError("sjw must be 1 - 4")
-
- return (sjw - 1) << 6 | brp - 1
+ """Bit timing register 0."""
+ return (self.sjw - 1) << 6 | self.brp - 1
@property
def btr1(self) -> int:
+ """Bit timing register 1."""
sam = 1 if self.nof_samples == 3 else 0
- tseg1 = self.tseg1
- tseg2 = self.tseg2
+ return sam << 7 | (self.tseg2 - 1) << 4 | self.tseg1 - 1
- if tseg1 < 1 or tseg1 > 16:
- raise ValueError("tseg1 must be 1 - 16")
- if tseg2 < 1 or tseg2 > 8:
- raise ValueError("tseg2 must be 1 - 8")
+ def oscillator_tolerance(
+ self,
+ node_loop_delay_ns: float = 250.0,
+ bus_length_m: float = 10.0,
+ ) -> float:
+ """Oscillator tolerance in percent according to ISO 11898-1.
- return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1
+ :param float node_loop_delay_ns:
+ Transceiver loop delay in nanoseconds.
+ :param float bus_length_m:
+ Bus length in meters.
+ """
+ delay_per_meter = 5
+ bidirectional_propagation_delay_ns = 2 * (
+ node_loop_delay_ns + delay_per_meter * bus_length_m
+ )
- def __str__(self) -> str:
- segments = []
- try:
- segments.append(f"{self.bitrate} bits/s")
- except ValueError:
- pass
- try:
- segments.append(f"sample point: {self.sample_point:.2f}%")
- except ValueError:
- pass
- try:
- segments.append(f"BRP: {self.brp}")
- except ValueError:
- pass
- try:
- segments.append(f"TSEG1: {self.tseg1}")
- except ValueError:
- pass
+ prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.tq)
+ nom_phase_seg1 = self.tseg1 - prop_seg
+ nom_phase_seg2 = self.tseg2
+ df_clock_list = [
+ _oscillator_tolerance_condition_1(nom_sjw=self.sjw, nbt=self.nbt),
+ _oscillator_tolerance_condition_2(
+ nbt=self.nbt,
+ nom_phase_seg1=nom_phase_seg1,
+ nom_phase_seg2=nom_phase_seg2,
+ ),
+ ]
+ return max(0.0, min(df_clock_list) * 100)
+
+ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
+ """Return a new :class:`~can.BitTiming` instance with the given *f_clock* but the same
+ bit rate and sample point.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :raises ValueError:
+ if no suitable bit timings were found.
+ """
+ # try the most simple solution first: another bitrate prescaler
try:
- segments.append(f"TSEG2: {self.tseg2}")
+ return BitTiming.from_bitrate_and_segments(
+ f_clock=f_clock,
+ bitrate=self.bitrate,
+ tseg1=self.tseg1,
+ tseg2=self.tseg2,
+ sjw=self.sjw,
+ nof_samples=self.nof_samples,
+ )
except ValueError:
pass
+
+ # create a new timing instance with the same sample point
+ bt = BitTiming.from_sample_point(
+ f_clock=f_clock, bitrate=self.bitrate, sample_point=self.sample_point
+ )
+ if abs(bt.sample_point - self.sample_point) > 1.0:
+ raise ValueError(
+ "f_clock change failed because of sample point discrepancy."
+ )
+ # adapt synchronization jump width, so it has the same size relative to bit time as self
+ sjw = int(round(self.sjw / self.nbt * bt.nbt))
+ sjw = max(1, min(4, bt.tseg2, sjw))
+ bt._data["sjw"] = sjw # pylint: disable=protected-access
+ bt._data["nof_samples"] = self.nof_samples # pylint: disable=protected-access
+ bt._validate() # pylint: disable=protected-access
+ return bt
+
+ def __str__(self) -> str:
+ segments = [
+ f"BR {self.bitrate} bit/s",
+ f"SP: {self.sample_point:.2f}%",
+ f"BRP: {self.brp}",
+ f"TSEG1: {self.tseg1}",
+ f"TSEG2: {self.tseg2}",
+ f"SJW: {self.sjw}",
+ f"BTR: {self.btr0:02X}{self.btr1:02X}h",
+ f"f_clock: {self.f_clock / 1e6:.0f}MHz",
+ ]
+ return ", ".join(segments)
+
+ def __repr__(self) -> str:
+ args = ", ".join(f"{key}={value}" for key, value in self.items())
+ return f"can.{self.__class__.__name__}({args})"
+
+ def __getitem__(self, key: str) -> int:
+ return cast(int, self._data.__getitem__(key))
+
+ def __len__(self) -> int:
+ return self._data.__len__()
+
+ def __iter__(self) -> Iterator[str]:
+ return self._data.__iter__()
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, BitTiming):
+ return False
+
+ return self._data == other._data
+
+
+class BitTimingFd(Mapping):
+ """Representation of a bit timing configuration for a CAN FD bus.
+
+ The class can be constructed in multiple ways, depending on the information
+ available. The preferred way is using CAN clock frequency, bit rate prescaler, tseg1,
+ tseg2 and sjw for both the arbitration (nominal) and data phase::
+
+ can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=59,
+ nom_tseg2=20,
+ nom_sjw=10,
+ data_brp=1,
+ data_tseg1=6,
+ data_tseg2=3,
+ data_sjw=2,
+ )
+
+ Alternatively you can set the bit rates instead of the bit rate prescalers::
+
+ can.BitTimingFd.from_bitrate_and_segments(
+ f_clock=80_000_000,
+ nom_bitrate=1_000_000,
+ nom_tseg1=59,
+ nom_tseg2=20,
+ nom_sjw=10,
+ data_bitrate=8_000_000,
+ data_tseg1=6,
+ data_tseg2=3,
+ data_sjw=2,
+ )
+
+ It is also possible to calculate the timings for a given
+ pair of arbitration and data sample points::
+
+ can.BitTimingFd.from_sample_point(
+ f_clock=80_000_000,
+ nom_bitrate=1_000_000,
+ nom_sample_point=75.0,
+ data_bitrate=8_000_000,
+ data_sample_point=70.0,
+ )
+ """
+
+ def __init__(
+ self,
+ f_clock: int,
+ nom_brp: int,
+ nom_tseg1: int,
+ nom_tseg2: int,
+ nom_sjw: int,
+ data_brp: int,
+ data_tseg1: int,
+ data_tseg2: int,
+ data_sjw: int,
+ ) -> None:
+ """
+ Initialize a BitTimingFd instance with the specified parameters.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :param int nom_brp:
+ Nominal (arbitration) phase bitrate prescaler.
+ :param int nom_tseg1:
+ Nominal phase Time segment 1, that is, the number of quanta from (but not including)
+ the Sync Segment to the sampling point.
+ :param int nom_tseg2:
+ Nominal phase Time segment 2, that is, the number of quanta from the sampling
+ point to the end of the bit.
+ :param int nom_sjw:
+ The Synchronization Jump Width for the nominal phase. This value determines
+ the maximum number of time quanta that the controller can resynchronize every bit.
+ :param int data_brp:
+ Data phase bitrate prescaler.
+ :param int data_tseg1:
+ Data phase Time segment 1, that is, the number of quanta from (but not including)
+ the Sync Segment to the sampling point.
+ :param int data_tseg2:
+ Data phase Time segment 2, that is, the number of quanta from the sampling
+ point to the end of the bit.
+ :param int data_sjw:
+ The Synchronization Jump Width for the data phase. This value determines
+ the maximum number of time quanta that the controller can resynchronize every bit.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
+ self._data: BitTimingFdDict = {
+ "f_clock": f_clock,
+ "nom_brp": nom_brp,
+ "nom_tseg1": nom_tseg1,
+ "nom_tseg2": nom_tseg2,
+ "nom_sjw": nom_sjw,
+ "data_brp": data_brp,
+ "data_tseg1": data_tseg1,
+ "data_tseg2": data_tseg2,
+ "data_sjw": data_sjw,
+ }
+ self._validate()
+
+ def _validate(self) -> None:
+ if self.nbt < 8:
+ raise ValueError(f"nominal bit time (={self.nbt}) must be at least 8.")
+
+ if self.dbt < 8:
+ raise ValueError(f"data bit time (={self.dbt}) must be at least 8.")
+
+ if not 1 <= self.nom_brp <= 256:
+ raise ValueError(
+ f"nominal bitrate prescaler (={self.nom_brp}) must be in [1...256]."
+ )
+
+ if not 1 <= self.data_brp <= 256:
+ raise ValueError(
+ f"data bitrate prescaler (={self.data_brp}) must be in [1...256]."
+ )
+
+ if not 5_000 <= self.nom_bitrate <= 2_000_000:
+ raise ValueError(
+ f"nom_bitrate (={self.nom_bitrate}) must be in [5,000...2,000,000]."
+ )
+
+ if not 25_000 <= self.data_bitrate <= 8_000_000:
+ raise ValueError(
+ f"data_bitrate (={self.data_bitrate}) must be in [25,000...8,000,000]."
+ )
+
+ if self.data_bitrate < self.nom_bitrate:
+ raise ValueError(
+ f"data_bitrate (={self.data_bitrate}) must be greater than or "
+ f"equal to nom_bitrate (={self.nom_bitrate})"
+ )
+
+ if not 2 <= self.nom_tseg1 <= 256:
+ raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...256].")
+
+ if not 1 <= self.nom_tseg2 <= 128:
+ raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [1...128].")
+
+ if not 1 <= self.data_tseg1 <= 32:
+ raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...32].")
+
+ if not 1 <= self.data_tseg2 <= 16:
+ raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [1...16].")
+
+ if not 1 <= self.nom_sjw <= 128:
+ raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...128].")
+
+ if self.nom_sjw > self.nom_tseg2:
+ raise ValueError(
+ f"nom_sjw (={self.nom_sjw}) must not be "
+ f"greater than nom_tseg2 (={self.nom_tseg2})."
+ )
+
+ if not 1 <= self.data_sjw <= 16:
+ raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...128].")
+
+ if self.data_sjw > self.data_tseg2:
+ raise ValueError(
+ f"data_sjw (={self.data_sjw}) must not be "
+ f"greater than data_tseg2 (={self.data_tseg2})."
+ )
+
+ if self.nom_sample_point < 50.0:
+ raise ValueError(
+ f"The arbitration sample point must be greater than or equal to 50% "
+ f"(nom_sample_point={self.nom_sample_point:.2f}%)."
+ )
+
+ if self.data_sample_point < 50.0:
+ raise ValueError(
+ f"The data sample point must be greater than or equal to 50% "
+ f"(data_sample_point={self.data_sample_point:.2f}%)."
+ )
+
+ @classmethod
+ def from_bitrate_and_segments(
+ cls,
+ f_clock: int,
+ nom_bitrate: int,
+ nom_tseg1: int,
+ nom_tseg2: int,
+ nom_sjw: int,
+ data_bitrate: int,
+ data_tseg1: int,
+ data_tseg2: int,
+ data_sjw: int,
+ ) -> "BitTimingFd":
+ """
+ Create a :class:`~can.BitTimingFd` instance with the bitrates and segments lengths.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :param int nom_bitrate:
+ Nominal (arbitration) phase bitrate in bit/s.
+ :param int nom_tseg1:
+ Nominal phase Time segment 1, that is, the number of quanta from (but not including)
+ the Sync Segment to the sampling point.
+ :param int nom_tseg2:
+ Nominal phase Time segment 2, that is, the number of quanta from the sampling
+ point to the end of the bit.
+ :param int nom_sjw:
+ The Synchronization Jump Width for the nominal phase. This value determines
+ the maximum number of time quanta that the controller can resynchronize every bit.
+ :param int data_bitrate:
+ Data phase bitrate in bit/s.
+ :param int data_tseg1:
+ Data phase Time segment 1, that is, the number of quanta from (but not including)
+ the Sync Segment to the sampling point.
+ :param int data_tseg2:
+ Data phase Time segment 2, that is, the number of quanta from the sampling
+ point to the end of the bit.
+ :param int data_sjw:
+ The Synchronization Jump Width for the data phase. This value determines
+ the maximum number of time quanta that the controller can resynchronize every bit.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
try:
- segments.append(f"SJW: {self.sjw}")
- except ValueError:
- pass
+ nom_brp = int(round(f_clock / (nom_bitrate * (1 + nom_tseg1 + nom_tseg2))))
+ data_brp = int(
+ round(f_clock / (data_bitrate * (1 + data_tseg1 + data_tseg2)))
+ )
+ except ZeroDivisionError:
+ raise ValueError("Invalid inputs.") from None
+
+ bt = cls(
+ f_clock=f_clock,
+ nom_brp=nom_brp,
+ nom_tseg1=nom_tseg1,
+ nom_tseg2=nom_tseg2,
+ nom_sjw=nom_sjw,
+ data_brp=data_brp,
+ data_tseg1=data_tseg1,
+ data_tseg2=data_tseg2,
+ data_sjw=data_sjw,
+ )
+
+ if abs(bt.nom_bitrate - nom_bitrate) > nom_bitrate / 256:
+ raise ValueError(
+ f"the effective nom. bitrate (={bt.nom_bitrate}) diverges "
+ f"from the requested nom. bitrate (={nom_bitrate})"
+ )
+
+ if abs(bt.data_bitrate - data_bitrate) > data_bitrate / 256:
+ raise ValueError(
+ f"the effective data bitrate (={bt.data_bitrate}) diverges "
+ f"from the requested data bitrate (={data_bitrate})"
+ )
+
+ return bt
+
+ @classmethod
+ def from_sample_point(
+ cls,
+ f_clock: int,
+ nom_bitrate: int,
+ nom_sample_point: float,
+ data_bitrate: int,
+ data_sample_point: float,
+ ) -> "BitTimingFd":
+ """Create a :class:`~can.BitTimingFd` instance for a given nominal/data sample point pair.
+
+ This function tries to find bit timings, which are close to the requested
+ sample points. It does not take physical bus properties into account, so the
+ calculated bus timings might not work properly for you.
+
+ The :func:`oscillator_tolerance` function might be helpful to evaluate the
+ bus timings.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :param int nom_bitrate:
+ Nominal bitrate in bit/s.
+ :param int nom_sample_point:
+ The sample point value of the arbitration phase in percent.
+ :param int data_bitrate:
+ Data bitrate in bit/s.
+ :param int data_sample_point:
+ The sample point value of the data phase in percent.
+ :raises ValueError:
+ if the arguments are invalid.
+ """
+ if nom_sample_point < 50.0:
+ raise ValueError(
+ f"nom_sample_point (={nom_sample_point}) must not be below 50%."
+ )
+
+ if data_sample_point < 50.0:
+ raise ValueError(
+ f"data_sample_point (={data_sample_point}) must not be below 50%."
+ )
+
+ possible_solutions: List[BitTimingFd] = []
+
+ for nom_brp in range(1, 257):
+ nbt = round(int(f_clock / (nom_bitrate * nom_brp)))
+ if nbt < 8:
+ break
+
+ effective_nom_bitrate = f_clock / (nbt * nom_brp)
+ if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256:
+ continue
+
+ nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1
+ # limit tseg1, so tseg2 is at least 1 TQ
+ nom_tseg1 = min(nom_tseg1, nbt - 2)
+ nom_tseg2 = nbt - nom_tseg1 - 1
+
+ nom_sjw = min(nom_tseg2, 128)
+
+ for data_brp in range(1, 257):
+ dbt = round(int(f_clock / (data_bitrate * data_brp)))
+ if dbt < 8:
+ break
+
+ effective_data_bitrate = f_clock / (dbt * data_brp)
+ if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256:
+ continue
+
+ data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1
+ # limit tseg1, so tseg2 is at least 1 TQ
+ data_tseg1 = min(data_tseg1, dbt - 2)
+ data_tseg2 = dbt - data_tseg1 - 1
+
+ data_sjw = min(data_tseg2, 16)
+
+ try:
+ bt = BitTimingFd(
+ f_clock=f_clock,
+ nom_brp=nom_brp,
+ nom_tseg1=nom_tseg1,
+ nom_tseg2=nom_tseg2,
+ nom_sjw=nom_sjw,
+ data_brp=data_brp,
+ data_tseg1=data_tseg1,
+ data_tseg2=data_tseg2,
+ data_sjw=data_sjw,
+ )
+ possible_solutions.append(bt)
+ except ValueError:
+ continue
+
+ if not possible_solutions:
+ raise ValueError("No suitable bit timings found.")
+
+ # prefer using the same prescaler for arbitration and data phase
+ same_prescaler = list(
+ filter(lambda x: x.nom_brp == x.data_brp, possible_solutions)
+ )
+ if same_prescaler:
+ possible_solutions = same_prescaler
+
+ # sort solutions
+ for key, reverse in (
+ # prefer low prescaler
+ (lambda x: x.nom_brp + x.data_brp, False),
+ # prefer same prescaler for arbitration and data
+ (lambda x: abs(x.nom_brp - x.data_brp), False),
+ # prefer low sample point deviation from requested values
+ (
+ lambda x: (
+ abs(x.nom_sample_point - nom_sample_point)
+ + abs(x.data_sample_point - data_sample_point)
+ ),
+ False,
+ ),
+ ):
+ possible_solutions.sort(key=key, reverse=reverse)
+
+ return possible_solutions[0]
+
+ @property
+ def f_clock(self) -> int:
+ """The CAN system clock frequency in Hz."""
+ return self._data["f_clock"]
+
+ @property
+ def nom_bitrate(self) -> int:
+ """Nominal (arbitration phase) bitrate."""
+ return int(round(self.f_clock / (self.nbt * self.nom_brp)))
+
+ @property
+ def nom_brp(self) -> int:
+ """Prescaler value for the arbitration phase."""
+ return self._data["nom_brp"]
+
+ @property
+ def nom_tq(self) -> int:
+ """Nominal time quantum in nanoseconds"""
+ return int(round(self.nom_brp / self.f_clock * 1e9))
+
+ @property
+ def nbt(self) -> int:
+ """Number of time quanta in a bit of the arbitration phase."""
+ return 1 + self.nom_tseg1 + self.nom_tseg2
+
+ @property
+ def nom_tseg1(self) -> int:
+ """Time segment 1 value of the arbitration phase.
+
+ This is the sum of the propagation time segment and the phase buffer segment 1.
+ """
+ return self._data["nom_tseg1"]
+
+ @property
+ def nom_tseg2(self) -> int:
+ """Time segment 2 value of the arbitration phase. Also known as phase buffer segment 2."""
+ return self._data["nom_tseg2"]
+
+ @property
+ def nom_sjw(self) -> int:
+ """Synchronization jump width of the arbitration phase.
+
+ The phase buffer segments may be shortened or lengthened by this value.
+ """
+ return self._data["nom_sjw"]
+
+ @property
+ def nom_sample_point(self) -> float:
+ """Sample point of the arbitration phase in percent."""
+ return 100.0 * (1 + self.nom_tseg1) / (1 + self.nom_tseg1 + self.nom_tseg2)
+
+ @property
+ def data_bitrate(self) -> int:
+ """Bitrate of the data phase in bit/s."""
+ return int(round(self.f_clock / (self.dbt * self.data_brp)))
+
+ @property
+ def data_brp(self) -> int:
+ """Prescaler value for the data phase."""
+ return self._data["data_brp"]
+
+ @property
+ def data_tq(self) -> int:
+ """Data time quantum in nanoseconds"""
+ return int(round(self.data_brp / self.f_clock * 1e9))
+
+ @property
+ def dbt(self) -> int:
+ """Number of time quanta in a bit of the data phase."""
+ return 1 + self.data_tseg1 + self.data_tseg2
+
+ @property
+ def data_tseg1(self) -> int:
+ """TSEG1 value of the data phase.
+
+ This is the sum of the propagation time segment and the phase buffer segment 1.
+ """
+ return self._data["data_tseg1"]
+
+ @property
+ def data_tseg2(self) -> int:
+ """TSEG2 value of the data phase. Also known as phase buffer segment 2."""
+ return self._data["data_tseg2"]
+
+ @property
+ def data_sjw(self) -> int:
+ """Synchronization jump width of the data phase.
+
+ The phase buffer segments may be shortened or lengthened by this value.
+ """
+ return self._data["data_sjw"]
+
+ @property
+ def data_sample_point(self) -> float:
+ """Sample point of the data phase in percent."""
+ return 100.0 * (1 + self.data_tseg1) / (1 + self.data_tseg1 + self.data_tseg2)
+
+ def oscillator_tolerance(
+ self,
+ node_loop_delay_ns: float = 250.0,
+ bus_length_m: float = 10.0,
+ ) -> float:
+ """Oscillator tolerance in percent according to ISO 11898-1.
+
+ :param float node_loop_delay_ns:
+ Transceiver loop delay in nanoseconds.
+ :param float bus_length_m:
+ Bus length in meters.
+ """
+ delay_per_meter = 5
+ bidirectional_propagation_delay_ns = 2 * (
+ node_loop_delay_ns + delay_per_meter * bus_length_m
+ )
+
+ prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.nom_tq)
+ nom_phase_seg1 = self.nom_tseg1 - prop_seg
+ nom_phase_seg2 = self.nom_tseg2
+
+ data_phase_seg2 = self.data_tseg2
+
+ df_clock_list = [
+ _oscillator_tolerance_condition_1(nom_sjw=self.nom_sjw, nbt=self.nbt),
+ _oscillator_tolerance_condition_2(
+ nbt=self.nbt,
+ nom_phase_seg1=nom_phase_seg1,
+ nom_phase_seg2=nom_phase_seg2,
+ ),
+ _oscillator_tolerance_condition_3(data_sjw=self.data_sjw, dbt=self.dbt),
+ _oscillator_tolerance_condition_4(
+ nom_phase_seg1=nom_phase_seg1,
+ nom_phase_seg2=nom_phase_seg2,
+ data_phase_seg2=data_phase_seg2,
+ nbt=self.nbt,
+ dbt=self.dbt,
+ data_brp=self.data_brp,
+ nom_brp=self.nom_brp,
+ ),
+ _oscillator_tolerance_condition_5(
+ data_sjw=self.data_sjw,
+ data_brp=self.data_brp,
+ nom_brp=self.nom_brp,
+ data_phase_seg2=data_phase_seg2,
+ nom_phase_seg2=nom_phase_seg2,
+ nbt=self.nbt,
+ dbt=self.dbt,
+ ),
+ ]
+ return max(0.0, min(df_clock_list) * 100)
+
+ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd":
+ """Return a new :class:`~can.BitTimingFd` instance with the given *f_clock* but the same
+ bit rates and sample points.
+
+ :param int f_clock:
+ The CAN system clock frequency in Hz.
+ :raises ValueError:
+ if no suitable bit timings were found.
+ """
+ # try the most simple solution first: another bitrate prescaler
try:
- segments.append(f"BTR: {self.btr0:02X}{self.btr1:02X}h")
+ return BitTimingFd.from_bitrate_and_segments(
+ f_clock=f_clock,
+ nom_bitrate=self.nom_bitrate,
+ nom_tseg1=self.nom_tseg1,
+ nom_tseg2=self.nom_tseg2,
+ nom_sjw=self.nom_sjw,
+ data_bitrate=self.data_bitrate,
+ data_tseg1=self.data_tseg1,
+ data_tseg2=self.data_tseg2,
+ data_sjw=self.data_sjw,
+ )
except ValueError:
pass
+
+ # create a new timing instance with the same sample points
+ bt = BitTimingFd.from_sample_point(
+ f_clock=f_clock,
+ nom_bitrate=self.nom_bitrate,
+ nom_sample_point=self.nom_sample_point,
+ data_bitrate=self.data_bitrate,
+ data_sample_point=self.data_sample_point,
+ )
+ if (
+ abs(bt.nom_sample_point - self.nom_sample_point) > 1.0
+ or abs(bt.data_sample_point - self.data_sample_point) > 1.0
+ ):
+ raise ValueError(
+ "f_clock change failed because of sample point discrepancy."
+ )
+ # adapt synchronization jump width, so it has the same size relative to bit time as self
+ nom_sjw = int(round(self.nom_sjw / self.nbt * bt.nbt))
+ nom_sjw = max(1, min(bt.nom_tseg2, nom_sjw))
+ bt._data["nom_sjw"] = nom_sjw # pylint: disable=protected-access
+ data_sjw = int(round(self.data_sjw / self.dbt * bt.dbt))
+ data_sjw = max(1, min(bt.data_tseg2, data_sjw))
+ bt._data["data_sjw"] = data_sjw # pylint: disable=protected-access
+ bt._validate() # pylint: disable=protected-access
+ return bt
+
+ def __str__(self) -> str:
+ segments = [
+ f"NBR: {self.nom_bitrate} bit/s",
+ f"NSP: {self.nom_sample_point:.2f}%",
+ f"NBRP: {self.nom_brp}",
+ f"NTSEG1: {self.nom_tseg1}",
+ f"NTSEG2: {self.nom_tseg2}",
+ f"NSJW: {self.nom_sjw}",
+ f"DBR: {self.data_bitrate} bit/s",
+ f"DSP: {self.data_sample_point:.2f}%",
+ f"DBRP: {self.data_brp}",
+ f"DTSEG1: {self.data_tseg1}",
+ f"DTSEG2: {self.data_tseg2}",
+ f"DSJW: {self.data_sjw}",
+ f"f_clock: {self.f_clock / 1e6:.0f}MHz",
+ ]
return ", ".join(segments)
def __repr__(self) -> str:
- kwargs = {}
- if self._f_clock:
- kwargs["f_clock"] = self._f_clock
- if self._bitrate:
- kwargs["bitrate"] = self._bitrate
- if self._brp:
- kwargs["brp"] = self._brp
- if self._tseg1:
- kwargs["tseg1"] = self._tseg1
- if self._tseg2:
- kwargs["tseg2"] = self._tseg2
- if self._sjw:
- kwargs["sjw"] = self._sjw
- if self._nof_samples != 1:
- kwargs["nof_samples"] = self._nof_samples
- args = ", ".join(f"{key}={value}" for key, value in kwargs.items())
- return f"can.BitTiming({args})"
+ args = ", ".join(f"{key}={value}" for key, value in self.items())
+ return f"can.{self.__class__.__name__}({args})"
+
+ def __getitem__(self, key: str) -> int:
+ return cast(int, self._data.__getitem__(key))
+
+ def __len__(self) -> int:
+ return self._data.__len__()
+
+ def __iter__(self) -> Iterator[str]:
+ return self._data.__iter__()
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, BitTimingFd):
+ return False
+
+ return self._data == other._data
+
+
+def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float:
+ """Arbitration phase - resynchronization"""
+ return nom_sjw / (2 * 10 * nbt)
+
+
+def _oscillator_tolerance_condition_2(
+ nbt: int, nom_phase_seg1: int, nom_phase_seg2: int
+) -> float:
+ """Arbitration phase - sampling of bit after error flag"""
+ return min(nom_phase_seg1, nom_phase_seg2) / (2 * (13 * nbt - nom_phase_seg2))
+
+
+def _oscillator_tolerance_condition_3(data_sjw: int, dbt: int) -> float:
+ """Data phase - resynchronization"""
+ return data_sjw / (2 * 10 * dbt)
+
+
+def _oscillator_tolerance_condition_4(
+ nom_phase_seg1: int,
+ nom_phase_seg2: int,
+ data_phase_seg2: int,
+ nbt: int,
+ dbt: int,
+ data_brp: int,
+ nom_brp: int,
+) -> float:
+ """Data phase - sampling of bit after error flag"""
+ return min(nom_phase_seg1, nom_phase_seg2) / (
+ 2 * ((6 * dbt - data_phase_seg2) * data_brp / nom_brp + 7 * nbt)
+ )
+
+
+def _oscillator_tolerance_condition_5(
+ data_sjw: int,
+ data_brp: int,
+ nom_brp: int,
+ nom_phase_seg2: int,
+ data_phase_seg2: int,
+ nbt: int,
+ dbt: int,
+) -> float:
+ """Data phase - bit rate switch"""
+ max_correctable_phase_shift = data_sjw - max(0.0, nom_brp / data_brp - 1)
+ time_between_resync = 2 * (
+ (2 * nbt - nom_phase_seg2) * nom_brp / data_brp + data_phase_seg2 + 4 * dbt
+ )
+ return max_correctable_phase_shift / time_between_resync
diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py
index 395e4b399..7150a60bd 100644
--- a/can/interfaces/canalystii.py
+++ b/can/interfaces/canalystii.py
@@ -1,24 +1,29 @@
import collections
from ctypes import c_ubyte
import logging
-import canalystii as driver
import time
-import warnings
from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union
-from can import BitTiming, BusABC, Message
-from can.exceptions import CanTimeoutError
+
+from can import BitTiming, BusABC, Message, BitTimingFd
+from can.exceptions import CanTimeoutError, CanInitializationError
from can.typechecking import CanFilters
+from can.util import deprecated_args_alias, check_or_adjust_timing_clock
+
+import canalystii as driver
logger = logging.getLogger(__name__)
class CANalystIIBus(BusABC):
+ @deprecated_args_alias(
+ deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing"
+ )
def __init__(
self,
channel: Union[int, Sequence[int], str] = (0, 1),
device: int = 0,
bitrate: Optional[int] = None,
- bit_timing: Optional[BitTiming] = None,
+ timing: Optional[Union[BitTiming, BitTimingFd]] = None,
can_filters: Optional[CanFilters] = None,
rx_queue_size: Optional[int] = None,
**kwargs: Dict[str, Any],
@@ -33,9 +38,12 @@ def __init__(
Optional USB device number. Default is 0 (first device found).
:param bitrate:
CAN bitrate in bits/second. Required unless the bit_timing argument is set.
- :param bit_timing:
- Optional BitTiming instance to use for custom bit timing setting.
- If this argument is set then it overrides the bitrate argument.
+ :param timing:
+ Optional :class:`~can.BitTiming` instance to use for custom bit timing setting.
+ If this argument is set then it overrides the bitrate argument. The
+ `f_clock` value of the timing instance must be set to 8_000_000 (8MHz)
+ for standard CAN.
+ CAN FD and the :class:`~can.BitTimingFd` class are not supported.
:param can_filters:
Optional filters for received CAN messages.
:param rx_queue_size:
@@ -44,8 +52,8 @@ def __init__(
"""
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
- if not (bitrate or bit_timing):
- raise ValueError("Either bitrate or bit_timing argument is required")
+ if not (bitrate or timing):
+ raise ValueError("Either bitrate or timing argument is required")
if isinstance(channel, str):
# Assume comma separated string of channels
@@ -63,17 +71,12 @@ def __init__(
self.device = driver.CanalystDevice(device_index=device)
for channel in self.channels:
- if bit_timing:
- try:
- if bit_timing.f_clock != 8_000_000:
- warnings.warn(
- f"bit_timing.f_clock value {bit_timing.f_clock} "
- "doesn't match expected device f_clock 8MHz."
- )
- except ValueError:
- pass # f_clock not specified
- self.device.init(
- channel, timing0=bit_timing.btr0, timing1=bit_timing.btr1
+ if isinstance(timing, BitTiming):
+ timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000])
+ self.device.init(channel, timing0=timing.btr0, timing1=timing.btr1)
+ elif isinstance(timing, BitTimingFd):
+ raise NotImplementedError(
+ f"CAN FD is not supported by {self.__class__.__name__}."
)
else:
self.device.init(channel, bitrate=bitrate)
diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py
index d735b7ee3..20e4d0cb7 100644
--- a/can/interfaces/cantact.py
+++ b/can/interfaces/cantact.py
@@ -4,14 +4,16 @@
import time
import logging
+from typing import Optional, Union, Any
from unittest.mock import Mock
-from can import BusABC, Message
+from can import BusABC, Message, BitTiming, BitTimingFd
from ..exceptions import (
CanInitializationError,
CanInterfaceNotImplementedError,
error_check,
)
+from ..util import deprecated_args_alias, check_or_adjust_timing_clock
logger = logging.getLogger(__name__)
@@ -42,16 +44,18 @@ def _detect_available_configs():
channels.append({"interface": "cantact", "channel": f"ch:{i}"})
return channels
+ @deprecated_args_alias(
+ deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing"
+ )
def __init__(
self,
- channel,
- bitrate=500000,
- poll_interval=0.01,
- monitor=False,
- bit_timing=None,
- _testing=False,
- **kwargs,
- ):
+ channel: int,
+ bitrate: int = 500_000,
+ poll_interval: float = 0.01,
+ monitor: bool = False,
+ timing: Optional[Union[BitTiming, BitTimingFd]] = None,
+ **kwargs: Any,
+ ) -> None:
"""
:param int channel:
Channel number (zero indexed, labeled on multi-channel devices)
@@ -59,16 +63,21 @@ def __init__(
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.
+ :param timing:
+ Optional :class:`~can.BitTiming` instance to use for custom bit timing setting.
+ If this argument is set then it overrides the bitrate argument. The
+ `f_clock` value of the timing instance must be set to 24_000_000 (24MHz)
+ for standard CAN.
+ CAN FD and the :class:`~can.BitTimingFd` class are not supported.
"""
- if _testing:
+ if kwargs.get("_testing", False):
self.interface = MockInterface()
else:
if cantact is None:
raise CanInterfaceNotImplementedError(
- "The CANtact module is not installed. Install it using `python -m pip install cantact`"
+ "The CANtact module is not installed. "
+ "Install it using `python -m pip install cantact`"
)
with error_check(
"Cannot create the cantact.Interface", CanInitializationError
@@ -80,18 +89,25 @@ def __init__(
# Configure the interface
with error_check("Cannot setup the cantact.Interface", CanInitializationError):
- if bit_timing is None:
- # use bitrate
- self.interface.set_bitrate(int(channel), int(bitrate))
- else:
+ if isinstance(timing, BitTiming):
+ timing = check_or_adjust_timing_clock(timing, valid_clocks=[24_000_000])
+
# 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),
+ int(timing.brp),
+ int(timing.tseg1),
+ int(timing.tseg2),
+ int(timing.sjw),
)
+ elif isinstance(timing, BitTimingFd):
+ raise NotImplementedError(
+ f"CAN FD is not supported by {self.__class__.__name__}."
+ )
+ else:
+ # use bitrate
+ 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()
diff --git a/can/typechecking.py b/can/typechecking.py
index 7aa4f7e5c..dc5c22270 100644
--- a/can/typechecking.py
+++ b/can/typechecking.py
@@ -48,3 +48,24 @@ class AutoDetectedConfig(typing_extensions.TypedDict):
ReadableBytesLike = typing.Union[bytes, bytearray, memoryview]
+
+
+class BitTimingDict(typing_extensions.TypedDict):
+ f_clock: int
+ brp: int
+ tseg1: int
+ tseg2: int
+ sjw: int
+ nof_samples: int
+
+
+class BitTimingFdDict(typing_extensions.TypedDict):
+ f_clock: int
+ nom_brp: int
+ nom_tseg1: int
+ nom_tseg2: int
+ nom_sjw: int
+ data_brp: int
+ data_tseg1: int
+ data_tseg2: int
+ data_sjw: int
diff --git a/can/util.py b/can/util.py
index e4a45f2d5..8f5cea0c4 100644
--- a/can/util.py
+++ b/can/util.py
@@ -1,23 +1,35 @@
"""
Utilities and configuration file parsing.
"""
-
+import copy
import functools
-import warnings
-from typing import Any, Callable, cast, Dict, Iterable, Tuple, Optional, Union
-from time import time, perf_counter, get_clock_info
import json
+import logging
import os
import os.path
import platform
import re
-import logging
+import warnings
from configparser import ConfigParser
+from time import time, perf_counter, get_clock_info
+from typing import (
+ Any,
+ Callable,
+ cast,
+ Dict,
+ Iterable,
+ Tuple,
+ Optional,
+ Union,
+ TypeVar,
+)
import can
-from .interfaces import VALID_INTERFACES
from . import typechecking
+from .bit_timing import BitTiming, BitTimingFd
+from .exceptions import CanInitializationError
from .exceptions import CanInterfaceNotImplementedError
+from .interfaces import VALID_INTERFACES
log = logging.getLogger("can.util")
@@ -226,6 +238,25 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
if not 0 < port < 65535:
raise ValueError("Port config must be inside 0-65535 range!")
+ if config.get("timing", None) is None:
+ try:
+ if set(typechecking.BitTimingFdDict.__annotations__).issubset(config):
+ config["timing"] = can.BitTimingFd(
+ **{
+ key: int(config[key])
+ for key in typechecking.BitTimingFdDict.__annotations__
+ }
+ )
+ elif set(typechecking.BitTimingDict.__annotations__).issubset(config):
+ config["timing"] = can.BitTiming(
+ **{
+ key: int(config[key])
+ for key in typechecking.BitTimingDict.__annotations__
+ }
+ )
+ except (ValueError, TypeError):
+ pass
+
if "bitrate" in config:
config["bitrate"] = int(config["bitrate"])
if "fd" in config:
@@ -343,6 +374,49 @@ def wrapper(*args, **kwargs):
return deco
+T = TypeVar("T", BitTiming, BitTimingFd)
+
+
+def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T:
+ """Adjusts the given timing instance to have an *f_clock* value that is within the
+ allowed values specified by *valid_clocks*. If the *f_clock* value of timing is
+ already within *valid_clocks*, then *timing* is returned unchanged.
+
+ :param timing:
+ The :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance to adjust.
+ :param valid_clocks:
+ An iterable of integers representing the valid *f_clock* values that the timing instance
+ can be changed to. The order of the values in *valid_clocks* determines the priority in
+ which they are tried, with earlier values being tried before later ones.
+ :return:
+ A new :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance with an
+ *f_clock* value within *valid_clocks*.
+ :raises ~can.exceptions.CanInitializationError:
+ If no compatible *f_clock* value can be found within *valid_clocks*.
+ """
+ if timing.f_clock in valid_clocks:
+ # create a copy so this function always returns a new instance
+ return copy.deepcopy(timing)
+
+ for clock in valid_clocks:
+ try:
+ # Try to use a different f_clock
+ adjusted_timing = timing.recreate_with_f_clock(clock)
+ warnings.warn(
+ f"Adjusted f_clock in {timing.__class__.__name__} from "
+ f"{timing.f_clock} to {adjusted_timing.f_clock}"
+ )
+ return adjusted_timing
+ except ValueError:
+ pass
+
+ raise CanInitializationError(
+ f"The specified timing.f_clock value {timing.f_clock} "
+ f"doesn't match any of the allowed device f_clock values: "
+ f"{', '.join([str(f) for f in valid_clocks])}"
+ ) from None
+
+
def _rename_kwargs(
func_name: str,
start: str,
diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst
index e1d2feeeb..b48a133b8 100644
--- a/doc/bit_timing.rst
+++ b/doc/bit_timing.rst
@@ -1,49 +1,112 @@
Bit Timing Configuration
========================
-The CAN protocol allows the bitrate, sample point and number of samples to be
-optimized for a given application. You can read more on Wikipedia_, Kvaser_
-and other sources.
-
-In most cases the recommended settings for a predefined set of common
-bitrates will work just fine. In some cases it may however be necessary to specify
-custom settings. The :class:`can.BitTiming` class can be used for this purpose to
-specify them in a relatively interface agnostic manner.
-
-It is also possible to specify the same settings for a CAN 2.0 bus
+.. attention::
+ This feature is experimental. The implementation might change in future
+ versions.
+
+The CAN protocol, specified in ISO 11898, allows the bitrate, sample point
+and number of samples to be optimized for a given application. These
+parameters, known as bit timings, can be adjusted to meet the requirements
+of the communication system and the physical communication channel.
+
+These parameters include:
+
+* **tseg1**: The time segment 1 (TSEG1) is the amount of time from the end
+ of the sync segment until the sample point. It is expressed in time quanta (TQ).
+* **tseg2**: The time segment 2 (TSEG2) is the amount of time from the
+ sample point until the end of the bit. It is expressed in TQ.
+* **sjw**: The synchronization jump width (SJW) is the maximum number
+ of TQ that the controller can resynchronize every bit.
+* **sample point**: The sample point is defined as the point in time
+ within a bit where the bus controller samples the bus for dominant or
+ recessive levels. It is typically expressed as a percentage of the bit time.
+ The sample point depends on the bus length and propagation time as well
+ as the information processing time of the nodes.
+
+.. figure:: images/bit_timing_light.svg
+ :align: center
+ :class: only-light
+
+.. figure:: images/bit_timing_dark.svg
+ :align: center
+ :class: only-dark
+
+ Bit Timing and Sample Point
+
+
+For example, consider a bit with a total duration of 8 TQ and a sample
+point at 75%. The values for TSEG1, TSEG2 and SJW would be 5, 2, and 2,
+respectively. The sample point would be 6 TQ after the start of the bit,
+leaving 2 TQ for the information processing by the bus nodes.
+
+.. note::
+ The values for TSEG1, TSEG2 and SJW are chosen such that the
+ sample point is at least 50% of the total bit time. This ensures that
+ there is sufficient time for the signal to stabilize before it is sampled.
+
+.. note::
+ In CAN FD, the arbitration (nominal) phase and the data phase can have
+ different bit rates. As a result, there are two separate sample points
+ to consider.
+
+Another important parameter is **f_clock**: The CAN system clock frequency
+in Hz. This frequency is used to derive the TQ size from the bit rate.
+The relationship is ``f_clock = (tseg1+tseg2+1) * bitrate * brp``.
+The bit rate prescaler value **brp** is usually determined by the controller
+and is chosen to ensure that the resulting bit time is an integer value.
+Typical CAN clock frequencies are 8-80 MHz.
+
+In most cases, the recommended settings for a predefined set of common
+bit rates will work just fine. In some cases, however, it may be necessary
+to specify custom bit timings. The :class:`~can.BitTiming` and
+:class:`~can.BitTimingFd` classes can be used for this purpose to specify
+bit timings in a relatively interface agnostic manner.
+
+It is possible to specify CAN 2.0 bit timings
using the config file:
.. code-block:: none
[default]
- bitrate=1000000
f_clock=8000000
- tseg1=5
- tseg2=2
- sjw=1
- nof_samples=1
-
-
-.. code-block:: none
-
- [default]
brp=1
tseg1=5
tseg2=2
sjw=1
nof_samples=1
+The same is possible for CAN FD:
.. code-block:: none
[default]
- btr0=0x00
- btr1=0x14
+ f_clock=80000000
+ nom_brp=1
+ nom_tseg1=119
+ nom_tseg2=40
+ nom_sjw=40
+ data_brp=1
+ data_tseg1=29
+ data_tseg2=10
+ data_sjw=10
+A :class:`dict` of the relevant config parameters can be easily obtained by calling
+``dict(timing)`` or ``{**timing}`` where ``timing`` is the :class:`~can.BitTiming` or
+:class:`~can.BitTimingFd` instance.
-.. autoclass:: can.BitTiming
+Check :doc:`configuration` for more information about saving and loading configurations.
-.. _Wikipedia: https://en.wikipedia.org/wiki/CAN_bus#Bit_timing
-.. _Kvaser: https://www.kvaser.com/about-can/the-can-protocol/can-bit-timing/
+.. autoclass:: can.BitTiming
+ :class-doc-from: both
+ :show-inheritance:
+ :members:
+ :member-order: bysource
+
+.. autoclass:: can.BitTimingFd
+ :class-doc-from: both
+ :show-inheritance:
+ :members:
+ :member-order: bysource
diff --git a/doc/conf.py b/doc/conf.py
index de08fc300..6ba661a30 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -120,10 +120,12 @@
# disable specific warnings
nitpick_ignore = [
# Ignore warnings for type aliases. Remove once Sphinx supports PEP613
+ ("py:class", "BusConfig"),
("py:class", "can.typechecking.BusConfig"),
("py:class", "can.typechecking.CanFilter"),
("py:class", "can.typechecking.CanFilterExtended"),
("py:class", "can.typechecking.AutoDetectedConfig"),
+ ("py:class", "can.util.T"),
# intersphinx fails to reference some builtins
("py:class", "asyncio.events.AbstractEventLoop"),
("py:class", "_thread.allocate_lock"),
diff --git a/doc/images/bit_timing_dark.svg b/doc/images/bit_timing_dark.svg
new file mode 100644
index 000000000..cc54a3f51
--- /dev/null
+++ b/doc/images/bit_timing_dark.svg
@@ -0,0 +1,96 @@
+
\ No newline at end of file
diff --git a/doc/images/bit_timing_light.svg b/doc/images/bit_timing_light.svg
new file mode 100644
index 000000000..eb021ea34
--- /dev/null
+++ b/doc/images/bit_timing_light.svg
@@ -0,0 +1,92 @@
+
\ No newline at end of file
diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py
index 2a9b1ac79..669308f9d 100644
--- a/test/test_bit_timing.py
+++ b/test/test_bit_timing.py
@@ -1,15 +1,20 @@
#!/usr/bin/env python
+import struct
+
+import pytest
+
import can
+from can.interfaces.pcan.pcan import PCAN_BITRATES
def test_sja1000():
"""Test some values obtained using other bit timing calculators."""
timing = can.BitTiming(
- f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2, nof_samples=3
+ f_clock=8_000_000, brp=4, tseg1=11, tseg2=4, sjw=2, nof_samples=3
)
- assert timing.f_clock == 8000000
- assert timing.bitrate == 125000
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 125_000
assert timing.brp == 4
assert timing.nbt == 16
assert timing.tseg1 == 11
@@ -20,9 +25,9 @@ def test_sja1000():
assert timing.btr0 == 0x43
assert timing.btr1 == 0xBA
- timing = can.BitTiming(f_clock=8000000, bitrate=500000, tseg1=13, tseg2=2, sjw=1)
- assert timing.f_clock == 8000000
- assert timing.bitrate == 500000
+ timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=13, tseg2=2, sjw=1)
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 500_000
assert timing.brp == 1
assert timing.nbt == 16
assert timing.tseg1 == 13
@@ -33,9 +38,9 @@ def test_sja1000():
assert timing.btr0 == 0x00
assert timing.btr1 == 0x1C
- timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1)
- assert timing.f_clock == 8000000
- assert timing.bitrate == 1000000
+ timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 1_000_000
assert timing.brp == 1
assert timing.nbt == 8
assert timing.tseg1 == 5
@@ -47,35 +52,116 @@ def test_sja1000():
assert timing.btr1 == 0x14
-def test_can_fd():
- timing = can.BitTiming(
- f_clock=80000000, bitrate=500000, tseg1=119, tseg2=40, sjw=40
+def test_from_bitrate_and_segments():
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=8_000_000, bitrate=125_000, tseg1=11, tseg2=4, sjw=2, nof_samples=3
)
- assert timing.f_clock == 80000000
- assert timing.bitrate == 500000
- assert timing.brp == 1
- assert timing.nbt == 160
- assert timing.tseg1 == 119
- assert timing.tseg2 == 40
- assert timing.sjw == 40
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 125_000
+ assert timing.brp == 4
+ assert timing.nbt == 16
+ assert timing.tseg1 == 11
+ assert timing.tseg2 == 4
+ assert timing.sjw == 2
+ assert timing.nof_samples == 3
assert timing.sample_point == 75
+ assert timing.btr0 == 0x43
+ assert timing.btr1 == 0xBA
- timing = can.BitTiming(
- f_clock=80000000, bitrate=2000000, tseg1=29, tseg2=10, sjw=10
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=8_000_000, bitrate=500_000, tseg1=13, tseg2=2, sjw=1
+ )
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 500_000
+ assert timing.brp == 1
+ assert timing.nbt == 16
+ assert timing.tseg1 == 13
+ assert timing.tseg2 == 2
+ assert timing.sjw == 1
+ assert timing.nof_samples == 1
+ assert timing.sample_point == 87.5
+ assert timing.btr0 == 0x00
+ assert timing.btr1 == 0x1C
+
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=2, sjw=1
)
- assert timing.f_clock == 80000000
- assert timing.bitrate == 2000000
+ assert timing.f_clock == 8_000_000
+ assert timing.bitrate == 1_000_000
assert timing.brp == 1
- assert timing.nbt == 40
- assert timing.tseg1 == 29
- assert timing.tseg2 == 10
- assert timing.sjw == 10
+ assert timing.nbt == 8
+ assert timing.tseg1 == 5
+ assert timing.tseg2 == 2
+ assert timing.sjw == 1
+ assert timing.nof_samples == 1
assert timing.sample_point == 75
+ assert timing.btr0 == 0x00
+ assert timing.btr1 == 0x14
+
+ timing = can.BitTimingFd.from_bitrate_and_segments(
+ f_clock=80_000_000,
+ nom_bitrate=500_000,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_bitrate=2_000_000,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+
+ assert timing.f_clock == 80_000_000
+ assert timing.nom_bitrate == 500_000
+ assert timing.nom_brp == 1
+ assert timing.nbt == 160
+ assert timing.nom_tseg1 == 119
+ assert timing.nom_tseg2 == 40
+ assert timing.nom_sjw == 40
+ assert timing.nom_sample_point == 75
+ assert timing.f_clock == 80_000_000
+ assert timing.data_bitrate == 2_000_000
+ assert timing.data_brp == 1
+ assert timing.dbt == 40
+ assert timing.data_tseg1 == 29
+ assert timing.data_tseg2 == 10
+ assert timing.data_sjw == 10
+ assert timing.data_sample_point == 75
+
+
+def test_can_fd():
+ timing = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+
+ assert timing.f_clock == 80_000_000
+ assert timing.nom_bitrate == 500_000
+ assert timing.nom_brp == 1
+ assert timing.nbt == 160
+ assert timing.nom_tseg1 == 119
+ assert timing.nom_tseg2 == 40
+ assert timing.nom_sjw == 40
+ assert timing.nom_sample_point == 75
+ assert timing.f_clock == 80_000_000
+ assert timing.data_bitrate == 2_000_000
+ assert timing.data_brp == 1
+ assert timing.dbt == 40
+ assert timing.data_tseg1 == 29
+ assert timing.data_tseg2 == 10
+ assert timing.data_sjw == 10
+ assert timing.data_sample_point == 75
def test_from_btr():
- timing = can.BitTiming(f_clock=8000000, btr0=0x00, btr1=0x14)
- assert timing.bitrate == 1000000
+ timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14)
+ assert timing.bitrate == 1_000_000
assert timing.brp == 1
assert timing.nbt == 8
assert timing.tseg1 == 5
@@ -86,9 +172,254 @@ def test_from_btr():
assert timing.btr1 == 0x14
+def test_btr_persistence():
+ f_clock = 8_000_000
+ for btr0btr1 in PCAN_BITRATES.values():
+ btr1, btr0 = struct.unpack("BB", btr0btr1)
+
+ t = can.BitTiming.from_registers(f_clock, btr0, btr1)
+ assert t.btr0 == btr0
+ assert t.btr1 == btr1
+
+
+def test_from_sample_point():
+ timing = can.BitTiming.from_sample_point(
+ f_clock=16_000_000,
+ bitrate=500_000,
+ sample_point=69.0,
+ )
+ assert timing.f_clock == 16_000_000
+ assert timing.bitrate == 500_000
+ assert 68 < timing.sample_point < 70
+
+ fd_timing = can.BitTimingFd.from_sample_point(
+ f_clock=80_000_000,
+ nom_bitrate=1_000_000,
+ nom_sample_point=75.0,
+ data_bitrate=8_000_000,
+ data_sample_point=70.0,
+ )
+ assert fd_timing.f_clock == 80_000_000
+ assert fd_timing.nom_bitrate == 1_000_000
+ assert 74 < fd_timing.nom_sample_point < 76
+ assert fd_timing.data_bitrate == 8_000_000
+ assert 69 < fd_timing.data_sample_point < 71
+
+ # check that there is a solution for every sample point
+ for sp in range(50, 100):
+ can.BitTiming.from_sample_point(
+ f_clock=16_000_000, bitrate=500_000, sample_point=sp
+ )
+
+ # check that there is a solution for every sample point
+ for nsp in range(50, 100):
+ for dsp in range(50, 100):
+ can.BitTimingFd.from_sample_point(
+ f_clock=80_000_000,
+ nom_bitrate=500_000,
+ nom_sample_point=nsp,
+ data_bitrate=2_000_000,
+ data_sample_point=dsp,
+ )
+
+
+def test_equality():
+ t1 = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14)
+ t2 = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1)
+ t3 = can.BitTiming(
+ f_clock=16_000_000, brp=2, tseg1=5, tseg2=2, sjw=1, nof_samples=1
+ )
+ assert t1 == t2
+ assert t1 != t3
+ assert t2 != t3
+ assert t1 != 10
+
+ t4 = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+ t5 = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+ t6 = can.BitTimingFd.from_sample_point(
+ f_clock=80_000_000,
+ nom_bitrate=1_000_000,
+ nom_sample_point=75.0,
+ data_bitrate=8_000_000,
+ data_sample_point=70.0,
+ )
+ assert t4 == t5
+ assert t4 != t6
+ assert t4 != t1
+
+
def test_string_representation():
- timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1)
- assert (
- str(timing)
- == "1000000 bits/s, sample point: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, BTR: 0014h"
+ timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
+ assert str(timing) == (
+ "BR 1000000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, "
+ "BTR: 0014h, f_clock: 8MHz"
+ )
+
+ fd_timing = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+ assert str(fd_timing) == (
+ "NBR: 500000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, "
+ "DBR: 2000000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, "
+ "f_clock: 80MHz"
+ )
+
+
+def test_repr():
+ timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
+ assert repr(timing) == (
+ "can.BitTiming(f_clock=8000000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1)"
+ )
+
+ fd_timing = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+ assert repr(fd_timing) == (
+ "can.BitTimingFd(f_clock=80000000, nom_brp=1, nom_tseg1=119, nom_tseg2=40, "
+ "nom_sjw=40, data_brp=1, data_tseg1=29, data_tseg2=10, data_sjw=10)"
+ )
+
+
+def test_mapping():
+ timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
+ timing_dict = dict(timing)
+ assert timing_dict["f_clock"] == timing["f_clock"]
+ assert timing_dict["brp"] == timing["brp"]
+ assert timing_dict["tseg1"] == timing["tseg1"]
+ assert timing_dict["tseg2"] == timing["tseg2"]
+ assert timing_dict["sjw"] == timing["sjw"]
+ assert timing == can.BitTiming(**timing_dict)
+
+ fd_timing = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+ fd_timing_dict = dict(fd_timing)
+ assert fd_timing_dict["f_clock"] == fd_timing["f_clock"]
+ assert fd_timing_dict["nom_brp"] == fd_timing["nom_brp"]
+ assert fd_timing_dict["nom_tseg1"] == fd_timing["nom_tseg1"]
+ assert fd_timing_dict["nom_tseg2"] == fd_timing["nom_tseg2"]
+ assert fd_timing_dict["nom_sjw"] == fd_timing["nom_sjw"]
+ assert fd_timing_dict["data_brp"] == fd_timing["data_brp"]
+ assert fd_timing_dict["data_tseg1"] == fd_timing["data_tseg1"]
+ assert fd_timing_dict["data_tseg2"] == fd_timing["data_tseg2"]
+ assert fd_timing_dict["data_sjw"] == fd_timing["data_sjw"]
+ assert fd_timing == can.BitTimingFd(**fd_timing_dict)
+
+
+def test_oscillator_tolerance():
+ timing = can.BitTiming(f_clock=16_000_000, brp=2, tseg1=10, tseg2=5, sjw=4)
+ osc_tol = timing.oscillator_tolerance(
+ node_loop_delay_ns=250,
+ bus_length_m=10.0,
+ )
+ assert osc_tol == pytest.approx(1.23, abs=1e-2)
+
+ fd_timing = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=5,
+ nom_tseg1=27,
+ nom_tseg2=4,
+ nom_sjw=4,
+ data_brp=5,
+ data_tseg1=6,
+ data_tseg2=1,
+ data_sjw=1,
+ )
+ osc_tol = fd_timing.oscillator_tolerance(
+ node_loop_delay_ns=250,
+ bus_length_m=10.0,
+ )
+ assert osc_tol == pytest.approx(0.48, abs=1e-2)
+
+
+def test_recreate_with_f_clock():
+ timing_8mhz = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
+ timing_16mhz = timing_8mhz.recreate_with_f_clock(f_clock=16_000_000)
+ assert timing_8mhz.bitrate == timing_16mhz.bitrate
+ assert timing_8mhz.sample_point == timing_16mhz.sample_point
+ assert (timing_8mhz.sjw / timing_8mhz.nbt) == pytest.approx(
+ timing_16mhz.sjw / timing_16mhz.nbt, abs=1e-3
+ )
+ assert timing_8mhz.nof_samples == timing_16mhz.nof_samples
+
+ timing_16mhz = can.BitTiming(
+ f_clock=16000000, brp=2, tseg1=12, tseg2=3, sjw=3, nof_samples=1
+ )
+ timing_8mhz = timing_16mhz.recreate_with_f_clock(f_clock=8_000_000)
+ assert timing_8mhz.bitrate == timing_16mhz.bitrate
+ assert timing_8mhz.sample_point == timing_16mhz.sample_point
+ assert (timing_8mhz.sjw / timing_8mhz.nbt) == pytest.approx(
+ timing_16mhz.sjw / timing_16mhz.nbt, abs=1e-2
+ )
+ assert timing_8mhz.nof_samples == timing_16mhz.nof_samples
+
+ fd_timing_80mhz = can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=5,
+ nom_tseg1=27,
+ nom_tseg2=4,
+ nom_sjw=4,
+ data_brp=5,
+ data_tseg1=6,
+ data_tseg2=1,
+ data_sjw=1,
+ )
+ fd_timing_60mhz = fd_timing_80mhz.recreate_with_f_clock(f_clock=60_000_000)
+ assert fd_timing_80mhz.nom_bitrate == fd_timing_60mhz.nom_bitrate
+ assert fd_timing_80mhz.nom_sample_point == pytest.approx(
+ fd_timing_60mhz.nom_sample_point, abs=1.0
+ )
+ assert (fd_timing_80mhz.nom_sjw / fd_timing_80mhz.nbt) == pytest.approx(
+ fd_timing_60mhz.nom_sjw / fd_timing_60mhz.nbt, abs=1e-2
+ )
+ assert fd_timing_80mhz.data_bitrate == fd_timing_60mhz.data_bitrate
+ assert fd_timing_80mhz.data_sample_point == pytest.approx(
+ fd_timing_60mhz.data_sample_point, abs=1.0
+ )
+ assert (fd_timing_80mhz.data_sjw / fd_timing_80mhz.dbt) == pytest.approx(
+ fd_timing_60mhz.data_sjw / fd_timing_60mhz.dbt, abs=1e-2
)
diff --git a/test/test_cantact.py b/test/test_cantact.py
index 4383fab37..2cc3e479c 100644
--- a/test/test_cantact.py
+++ b/test/test_cantact.py
@@ -23,8 +23,8 @@ def test_bus_creation(self):
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, interface="cantact", bit_timing=bt, _testing=True)
+ bt = can.BitTiming(f_clock=24_000_000, brp=3, tseg1=13, tseg2=2, sjw=1)
+ bus = can.Bus(channel=0, interface="cantact", timing=bt, _testing=True)
self.assertIsInstance(bus, cantact.CantactBus)
cantact.MockInterface.set_bitrate.assert_not_called()
cantact.MockInterface.set_bit_timing.assert_called()
diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py
index 467473671..4d1d3eb84 100755
--- a/test/test_interface_canalystii.py
+++ b/test/test_interface_canalystii.py
@@ -39,8 +39,10 @@ def test_initialize_single_channel_only(self):
def test_initialize_with_timing_registers(self):
with create_mock_device() as mock_device:
instance = mock_device.return_value
- timing = can.BitTiming(btr0=0x03, btr1=0x6F)
- bus = CANalystIIBus(bitrate=None, bit_timing=timing)
+ timing = can.BitTiming.from_registers(
+ f_clock=8_000_000, btr0=0x03, btr1=0x6F
+ )
+ bus = CANalystIIBus(bitrate=None, timing=timing)
instance.init.assert_has_calls(
[
call(0, timing0=0x03, timing1=0x6F),
@@ -50,15 +52,9 @@ def test_initialize_with_timing_registers(self):
def test_missing_bitrate(self):
with self.assertRaises(ValueError) as cm:
- bus = CANalystIIBus(0, bitrate=None, bit_timing=None)
+ bus = CANalystIIBus(0, bitrate=None, timing=None)
self.assertIn("bitrate", str(cm.exception))
- def test_invalid_bit_timing(self):
- with create_mock_device() as mock_device:
- with self.assertRaises(ValueError) as cm:
- invalid_timings = can.BitTiming()
- CANalystIIBus(0, bit_timing=invalid_timings)
-
def test_receive_message(self):
driver_message = driver.Message(
can_id=0x333,
diff --git a/test/test_util.py b/test/test_util.py
index 70941f23f..a738e5c6a 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -5,11 +5,14 @@
import pytest
+from can import BitTiming, BitTimingFd
+from can.exceptions import CanInitializationError
from can.util import (
_create_bus_config,
_rename_kwargs,
channel2int,
deprecated_args_alias,
+ check_or_adjust_timing_clock,
)
@@ -167,3 +170,95 @@ def test_channel2int(self) -> None:
self.assertEqual(42, channel2int("42"))
self.assertEqual(None, channel2int("can"))
self.assertEqual(None, channel2int("can0a"))
+
+
+class TestCheckAdjustTimingClock(unittest.TestCase):
+ def test_adjust_timing(self):
+ timing = BitTiming(f_clock=80_000_000, brp=10, tseg1=13, tseg2=2, sjw=1)
+
+ # Check identity case
+ new_timing = check_or_adjust_timing_clock(timing, valid_clocks=[80_000_000])
+ assert timing == new_timing
+
+ with pytest.warns(UserWarning) as record:
+ new_timing = check_or_adjust_timing_clock(
+ timing, valid_clocks=[8_000_000, 24_000_000]
+ )
+ assert len(record) == 1
+ assert (
+ record[0].message.args[0]
+ == "Adjusted f_clock in BitTiming from 80000000 to 8000000"
+ )
+ assert new_timing.__class__ == BitTiming
+ assert new_timing.f_clock == 8_000_000
+ assert new_timing.bitrate == timing.bitrate
+ assert new_timing.tseg1 == timing.tseg1
+ assert new_timing.tseg2 == timing.tseg2
+ assert new_timing.sjw == timing.sjw
+
+ # Check that order is preserved
+ with pytest.warns(UserWarning) as record:
+ new_timing = check_or_adjust_timing_clock(
+ timing, valid_clocks=[24_000_000, 8_000_000]
+ )
+ assert new_timing.f_clock == 24_000_000
+ assert len(record) == 1
+ assert (
+ record[0].message.args[0]
+ == "Adjusted f_clock in BitTiming from 80000000 to 24000000"
+ )
+
+ # Check that order is preserved for all valid clock rates
+ with pytest.warns(UserWarning) as record:
+ new_timing = check_or_adjust_timing_clock(
+ timing, valid_clocks=[8_000, 24_000_000, 8_000_000]
+ )
+ assert new_timing.f_clock == 24_000_000
+ assert len(record) == 1
+ assert (
+ record[0].message.args[0]
+ == "Adjusted f_clock in BitTiming from 80000000 to 24000000"
+ )
+
+ with pytest.raises(CanInitializationError):
+ check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000])
+
+ def test_adjust_timing_fd(self):
+ timing = BitTimingFd(
+ f_clock=160_000_000,
+ nom_brp=2,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=2,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ )
+
+ # Check identity case
+ new_timing = check_or_adjust_timing_clock(timing, valid_clocks=[160_000_000])
+ assert timing == new_timing
+
+ with pytest.warns(UserWarning) as record:
+ new_timing = check_or_adjust_timing_clock(
+ timing, valid_clocks=[8_000, 80_000_000]
+ )
+ assert len(record) == 1
+ assert (
+ record[0].message.args[0]
+ == "Adjusted f_clock in BitTimingFd from 160000000 to 80000000"
+ )
+ assert new_timing.__class__ == BitTimingFd
+ assert new_timing.f_clock == 80_000_000
+ assert new_timing.nom_bitrate == 500_000
+ assert new_timing.nom_tseg1 == 119
+ assert new_timing.nom_tseg2 == 40
+ assert new_timing.nom_sjw == 40
+ assert new_timing.data_bitrate == 2_000_000
+ assert new_timing.data_tseg1 == 29
+ assert new_timing.data_tseg2 == 10
+ assert new_timing.data_sjw == 10
+
+ with pytest.raises(CanInitializationError):
+ check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000])
From 86a91bb25d12a97ec5c7632c6f745cf6c079c288 Mon Sep 17 00:00:00 2001
From: Martin Thompson
Date: Wed, 25 Jan 2023 21:42:44 +0000
Subject: [PATCH 227/475] Add VN5611 HWTYPE (#1501)
---
can/interfaces/vector/xldefine.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py
index 5a1084f48..aa1a6740c 100644
--- a/can/interfaces/vector/xldefine.py
+++ b/can/interfaces/vector/xldefine.py
@@ -289,6 +289,7 @@ class XL_HardwareType(IntEnum):
XL_HWTYPE_VN7570 = 67
XL_HWTYPE_VN5650 = 68
XL_HWTYPE_IPCLIENT = 69
+ XL_HWTYPE_VN5611 = 70
XL_HWTYPE_IPSERVER = 71
XL_HWTYPE_VX1121 = 73
XL_HWTYPE_VX1131 = 75
From dcf15c962786b969fa45232e176ed02eabc889b4 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Wed, 25 Jan 2023 23:03:01 +0100
Subject: [PATCH 228/475] improve robustness against unknown HardwareType
values (#1502)
---
can/interfaces/vector/canlib.py | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 5c7f1a8ad..ead3ee933 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -299,19 +299,21 @@ def _find_global_channel_idx(
channel_configs: List["VectorChannelConfig"],
) -> int:
if serial is not None:
- hw_type: Optional[xldefine.XL_HardwareType] = None
+ serial_found = False
for channel_config in channel_configs:
if channel_config.serial_number != serial:
continue
- hw_type = xldefine.XL_HardwareType(channel_config.hw_type)
+ serial_found = True
if channel_config.hw_channel == channel:
return channel_config.channel_index
- if hw_type is None:
+ if not serial_found:
err_msg = f"No interface with serial {serial} found."
else:
- err_msg = f"Channel {channel} not found on interface {hw_type.name} ({serial})."
+ err_msg = (
+ f"Channel {channel} not found on interface with serial {serial}."
+ )
raise CanInitializationError(
err_msg, error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT
)
@@ -915,7 +917,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None:
@staticmethod
def get_application_config(
app_name: str, app_channel: int
- ) -> Tuple[xldefine.XL_HardwareType, int, int]:
+ ) -> Tuple[Union[int, xldefine.XL_HardwareType], int, int]:
"""Retrieve information for an application in Vector Hardware Configuration.
:param app_name:
@@ -955,13 +957,13 @@ def get_application_config(
),
function="xlGetApplConfig",
) from None
- return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value
+ return _hw_type(hw_type.value), hw_index.value, hw_channel.value
@staticmethod
def set_application_config(
app_name: str,
app_channel: int,
- hw_type: xldefine.XL_HardwareType,
+ hw_type: Union[int, xldefine.XL_HardwareType],
hw_index: int,
hw_channel: int,
**kwargs: Any,
@@ -1055,7 +1057,7 @@ class VectorChannelConfig(NamedTuple):
"""NamedTuple which contains the channel properties from Vector XL API."""
name: str
- hw_type: xldefine.XL_HardwareType
+ hw_type: Union[int, xldefine.XL_HardwareType]
hw_index: int
hw_channel: int
channel_index: int
@@ -1128,7 +1130,7 @@ def get_channel_configs() -> List[VectorChannelConfig]:
xlcc: xlclass.XLchannelConfig = driver_config.channel[i]
vcc = VectorChannelConfig(
name=xlcc.name.decode(),
- hw_type=xldefine.XL_HardwareType(xlcc.hwType),
+ hw_type=_hw_type(xlcc.hwType),
hw_index=xlcc.hwIndex,
hw_channel=xlcc.hwChannel,
channel_index=xlcc.channelIndex,
@@ -1148,3 +1150,11 @@ def get_channel_configs() -> List[VectorChannelConfig]:
)
channel_list.append(vcc)
return channel_list
+
+
+def _hw_type(hw_type: int) -> Union[int, xldefine.XL_HardwareType]:
+ try:
+ return xldefine.XL_HardwareType(hw_type)
+ except ValueError:
+ LOG.warning(f'Unknown XL_HardwareType value "{hw_type}"')
+ return hw_type
From 1dddc1dbd748e3854398210aa32ecfc3b33b71ac Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 27 Jan 2023 17:41:42 +0100
Subject: [PATCH 229/475] Automatic type conversion for config values (#1499)
---
can/logger.py | 14 ++------------
can/util.py | 37 +++++++++++++++++++++++++++++--------
test/test_load_config.py | 37 +++++++++++++++++++++----------------
test/test_util.py | 25 +++++++++++++++++++++++++
4 files changed, 77 insertions(+), 36 deletions(-)
diff --git a/can/logger.py b/can/logger.py
index 55e67b27e..9448fe6b4 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -8,6 +8,7 @@
import can
from can.io import BaseRotatingLogger
from can.io.generic import MessageWriter
+from can.util import cast_from_string
from . import Bus, BusState, Logger, SizedRotatingLogger
from .typechecking import CanFilter, CanFilters
@@ -134,18 +135,7 @@ def _split_arg(_arg: str) -> Tuple[str, str]:
args: Dict[str, Union[str, int, float, bool]] = {}
for key, string_val in map(_split_arg, unknown_args):
- if re.match(r"^[-+]?\d+$", string_val):
- # value is integer
- args[key] = int(string_val)
- elif re.match(r"^[-+]?\d*\.\d+$", string_val):
- # value is float
- args[key] = float(string_val)
- elif re.match(r"^(?:True|False)$", string_val):
- # value is bool
- args[key] = string_val == "True"
- else:
- # value is string
- args[key] = string_val
+ args[key] = cast_from_string(string_val)
return args
diff --git a/can/util.py b/can/util.py
index 8f5cea0c4..42d99272e 100644
--- a/can/util.py
+++ b/can/util.py
@@ -147,7 +147,7 @@ def load_config(
It may set other values that are passed through.
:param context:
- Extra 'context' pass to config sources. This can be use to section
+ Extra 'context' pass to config sources. This can be used to section
other than 'default' in the configuration file.
:return:
@@ -197,9 +197,12 @@ def load_config(
cfg["interface"] = cfg["bustype"]
del cfg["bustype"]
# copy all new parameters
- for key in cfg:
+ for key, val in cfg.items():
if key not in config:
- config[key] = cfg[key]
+ if isinstance(val, str):
+ config[key] = cast_from_string(val)
+ else:
+ config[key] = cfg[key]
bus_config = _create_bus_config(config)
can.log.debug("can config: %s", bus_config)
@@ -257,12 +260,8 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
except (ValueError, TypeError):
pass
- if "bitrate" in config:
- config["bitrate"] = int(config["bitrate"])
if "fd" in config:
- config["fd"] = config["fd"] not in ("0", "False", "false", False)
- if "data_bitrate" in config:
- config["data_bitrate"] = int(config["data_bitrate"])
+ config["fd"] = config["fd"] not in (0, False)
return cast(typechecking.BusConfig, config)
@@ -478,6 +477,28 @@ def time_perfcounter_correlation() -> Tuple[float, float]:
return t1, performance_counter
+def cast_from_string(string_val: str) -> Union[str, int, float, bool]:
+ """Perform trivial type conversion from :class:`str` values.
+
+ :param string_val:
+ the string, that shall be converted
+ """
+ if re.match(r"^[-+]?\d+$", string_val):
+ # value is integer
+ return int(string_val)
+
+ if re.match(r"^[-+]?\d*\.\d+(?:e[-+]?\d+)?$", string_val):
+ # value is float
+ return float(string_val)
+
+ if re.match(r"^(?:True|False)$", string_val, re.IGNORECASE):
+ # value is bool
+ return string_val.lower() == "true"
+
+ # value is string
+ return string_val
+
+
if __name__ == "__main__":
print("Searching for configuration named:")
print("\n".join(CONFIG_FILES))
diff --git a/test/test_load_config.py b/test/test_load_config.py
index 1bfba450a..3c850a730 100644
--- a/test/test_load_config.py
+++ b/test/test_load_config.py
@@ -1,20 +1,25 @@
#!/usr/bin/env python
-import os
import shutil
import tempfile
import unittest
+import unittest.mock
from tempfile import NamedTemporaryFile
import can
class LoadConfigTest(unittest.TestCase):
- configuration = {
+ configuration_in = {
"default": {"interface": "serial", "channel": "0"},
"one": {"interface": "kvaser", "channel": "1", "bitrate": 100000},
"two": {"channel": "2"},
}
+ configuration_out = {
+ "default": {"interface": "serial", "channel": 0},
+ "one": {"interface": "kvaser", "channel": 1, "bitrate": 100000},
+ "two": {"channel": 2},
+ }
def setUp(self):
# Create a temporary directory
@@ -31,7 +36,7 @@ def _gen_configration_file(self, sections):
content = []
for section in sections:
content.append(f"[{section}]")
- for k, v in self.configuration[section].items():
+ for k, v in self.configuration_in[section].items():
content.append(f"{k} = {v}")
tmp_config_file.write("\n".join(content))
return tmp_config_file.name
@@ -42,43 +47,43 @@ def _dict_to_env(self, d):
def test_config_default(self):
tmp_config = self._gen_configration_file(["default"])
config = can.util.load_config(path=tmp_config)
- self.assertEqual(config, self.configuration["default"])
+ self.assertEqual(config, self.configuration_out["default"])
def test_config_whole_default(self):
- tmp_config = self._gen_configration_file(self.configuration)
+ tmp_config = self._gen_configration_file(self.configuration_in)
config = can.util.load_config(path=tmp_config)
- self.assertEqual(config, self.configuration["default"])
+ self.assertEqual(config, self.configuration_out["default"])
def test_config_whole_context(self):
- tmp_config = self._gen_configration_file(self.configuration)
+ tmp_config = self._gen_configration_file(self.configuration_in)
config = can.util.load_config(path=tmp_config, context="one")
- self.assertEqual(config, self.configuration["one"])
+ self.assertEqual(config, self.configuration_out["one"])
def test_config_merge_context(self):
- tmp_config = self._gen_configration_file(self.configuration)
+ tmp_config = self._gen_configration_file(self.configuration_in)
config = can.util.load_config(path=tmp_config, context="two")
- expected = self.configuration["default"]
- expected.update(self.configuration["two"])
+ expected = self.configuration_out["default"].copy()
+ expected.update(self.configuration_out["two"])
self.assertEqual(config, expected)
def test_config_merge_environment_to_context(self):
- tmp_config = self._gen_configration_file(self.configuration)
+ tmp_config = self._gen_configration_file(self.configuration_in)
env_data = {"interface": "serial", "bitrate": 125000}
env_dict = self._dict_to_env(env_data)
with unittest.mock.patch.dict("os.environ", env_dict):
config = can.util.load_config(path=tmp_config, context="one")
- expected = self.configuration["one"]
+ expected = self.configuration_out["one"].copy()
expected.update(env_data)
self.assertEqual(config, expected)
def test_config_whole_environment(self):
- tmp_config = self._gen_configration_file(self.configuration)
+ tmp_config = self._gen_configration_file(self.configuration_in)
env_data = {"interface": "socketcan", "channel": "3", "bitrate": 250000}
env_dict = self._dict_to_env(env_data)
with unittest.mock.patch.dict("os.environ", env_dict):
config = can.util.load_config(path=tmp_config, context="one")
- expected = self.configuration["one"]
- expected.update(env_data)
+ expected = self.configuration_out["one"].copy()
+ expected.update({"interface": "socketcan", "channel": 3, "bitrate": 250000})
self.assertEqual(config, expected)
diff --git a/test/test_util.py b/test/test_util.py
index a738e5c6a..88349d974 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -13,6 +13,7 @@
channel2int,
deprecated_args_alias,
check_or_adjust_timing_clock,
+ cast_from_string,
)
@@ -262,3 +263,27 @@ def test_adjust_timing_fd(self):
with pytest.raises(CanInitializationError):
check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000])
+
+
+class TestCastFromString(unittest.TestCase):
+ def test_cast_from_string(self) -> None:
+ self.assertEqual(1, cast_from_string("1"))
+ self.assertEqual(-1, cast_from_string("-1"))
+ self.assertEqual(0, cast_from_string("-0"))
+ self.assertEqual(1.1, cast_from_string("1.1"))
+ self.assertEqual(-1.1, cast_from_string("-1.1"))
+ self.assertEqual(0.1, cast_from_string(".1"))
+ self.assertEqual(10.0, cast_from_string(".1e2"))
+ self.assertEqual(0.001, cast_from_string(".1e-2"))
+ self.assertEqual(-0.001, cast_from_string("-.1e-2"))
+ self.assertEqual("text", cast_from_string("text"))
+ self.assertEqual("", cast_from_string(""))
+ self.assertEqual("can0", cast_from_string("can0"))
+ self.assertEqual("0can", cast_from_string("0can"))
+ self.assertEqual(False, cast_from_string("false"))
+ self.assertEqual(False, cast_from_string("False"))
+ self.assertEqual(True, cast_from_string("true"))
+ self.assertEqual(True, cast_from_string("True"))
+
+ with self.assertRaises(TypeError):
+ cast_from_string(None)
From d2abd34b7868e4b47a7dfc0ebb1fbcd9c3c5a174 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 27 Jan 2023 18:44:56 +0100
Subject: [PATCH 230/475] Update XL_HardwareType (#1509)
---
can/interfaces/vector/xldefine.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py
index aa1a6740c..e2fd288b9 100644
--- a/can/interfaces/vector/xldefine.py
+++ b/can/interfaces/vector/xldefine.py
@@ -291,7 +291,9 @@ class XL_HardwareType(IntEnum):
XL_HWTYPE_IPCLIENT = 69
XL_HWTYPE_VN5611 = 70
XL_HWTYPE_IPSERVER = 71
+ XL_HWTYPE_VN5612 = 72
XL_HWTYPE_VX1121 = 73
+ XL_HWTYPE_VN5601 = 74
XL_HWTYPE_VX1131 = 75
XL_HWTYPE_VT6204 = 77
XL_HWTYPE_VN1630_LOG = 79
@@ -318,6 +320,8 @@ class XL_HardwareType(IntEnum):
XL_HWTYPE_VN1531 = 113
XL_HWTYPE_VX1161A = 114
XL_HWTYPE_VX1161B = 115
+ XL_HWTYPE_VGNSS = 116
+ XL_HWTYPE_VXLAPINIC = 118
XL_MAX_HWTYPE = 120
From 7a4c6f80782add361cf0c95613559ace408f17c4 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Fri, 27 Jan 2023 22:19:42 +0100
Subject: [PATCH 231/475] Add BitTiming/BitTimingFd support to VectorBus
(#1470)
* add BitTiming parameter to VectorBus
* use correct interface_version
* implement tests for bittiming classes with vector
---
can/interfaces/vector/canlib.py | 51 +++++++++--
test/test_vector.py | 148 ++++++++++++++++++++++++++++++++
2 files changed, 194 insertions(+), 5 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index ead3ee933..287f45437 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -37,12 +37,20 @@
# Import Modules
# ==============
-from can import BusABC, Message, CanInterfaceNotImplementedError, CanInitializationError
+from can import (
+ BusABC,
+ Message,
+ CanInterfaceNotImplementedError,
+ CanInitializationError,
+ BitTiming,
+ BitTimingFd,
+)
from can.util import (
len2dlc,
dlc2len,
deprecated_args_alias,
time_perfcounter_correlation,
+ check_or_adjust_timing_clock,
)
from can.typechecking import AutoDetectedConfig, CanFilters
@@ -86,6 +94,7 @@ def __init__(
can_filters: Optional[CanFilters] = None,
poll_interval: float = 0.01,
receive_own_messages: bool = False,
+ timing: Optional[Union[BitTiming, BitTimingFd]] = None,
bitrate: Optional[int] = None,
rx_queue_size: int = 2**14,
app_name: Optional[str] = "CANalyzer",
@@ -108,6 +117,15 @@ def __init__(
See :class:`can.BusABC`.
:param receive_own_messages:
See :class:`can.BusABC`.
+ :param timing:
+ An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd`
+ to specify the bit timing parameters for the VectorBus interface. The
+ `f_clock` value of the timing instance must be set to 16.000.000 (16MHz)
+ for standard CAN or 80.000.000 (80MHz) for CAN FD. If this parameter is provided,
+ it takes precedence over all other timing-related parameters.
+ Otherwise, the bit timing can be specified using the following parameters:
+ `bitrate` for standard CAN or `fd`, `data_bitrate`, `sjw_abr`, `tseg1_abr`,
+ `tseg2_abr`, `sjw_dbr`, `tseg1_dbr`, and `tseg2_dbr` for CAN FD.
:param poll_interval:
Poll interval in seconds.
:param bitrate:
@@ -184,7 +202,7 @@ def __init__(
channel_configs = get_channel_configs()
self.mask = 0
- self.fd = fd
+ self.fd = isinstance(timing, BitTimingFd) if timing else fd
self.channel_masks: Dict[int, int] = {}
self.index_to_channel: Dict[int, int] = {}
@@ -204,12 +222,12 @@ def __init__(
permission_mask = xlclass.XLaccess()
# Set mask to request channel init permission if needed
- if bitrate or fd:
+ if bitrate or fd or timing:
permission_mask.value = self.mask
interface_version = (
xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4
- if fd
+ if self.fd
else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION
)
@@ -233,7 +251,30 @@ def __init__(
# set CAN settings
for channel in self.channels:
- if fd:
+ if isinstance(timing, BitTiming):
+ timing = check_or_adjust_timing_clock(timing, [16_000_000])
+ self._set_bitrate_can(
+ channel=channel,
+ bitrate=timing.bitrate,
+ sjw=timing.sjw,
+ tseg1=timing.tseg1,
+ tseg2=timing.tseg2,
+ sam=timing.nof_samples,
+ )
+ elif isinstance(timing, BitTimingFd):
+ timing = check_or_adjust_timing_clock(timing, [80_000_000])
+ self._set_bitrate_canfd(
+ channel=channel,
+ bitrate=timing.nom_bitrate,
+ data_bitrate=timing.data_bitrate,
+ sjw_abr=timing.nom_sjw,
+ tseg1_abr=timing.nom_tseg1,
+ tseg2_abr=timing.nom_tseg2,
+ sjw_dbr=timing.data_sjw,
+ tseg1_dbr=timing.data_tseg1,
+ tseg2_dbr=timing.data_tseg2,
+ )
+ elif fd:
self._set_bitrate_canfd(
channel=channel,
bitrate=bitrate,
diff --git a/test/test_vector.py b/test/test_vector.py
index 02c3c336d..fd95cef8a 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -270,6 +270,154 @@ def test_bus_creation_fd_bitrate_timings() -> None:
bus.shutdown()
+def test_bus_creation_timing_mocked(mock_xldriver) -> None:
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=16_000_000,
+ bitrate=125_000,
+ tseg1=13,
+ tseg2=2,
+ sjw=1,
+ )
+ bus = can.Bus(channel=0, interface="vector", timing=timing, _testing=True)
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelParams.assert_called()
+ chip_params = (
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelParams.call_args[0]
+ )[2]
+ assert chip_params.bitRate == 125_000
+ assert chip_params.sjw == 1
+ assert chip_params.tseg1 == 13
+ assert chip_params.tseg2 == 2
+ assert chip_params.sam == 1
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation_timing() -> None:
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=16_000_000,
+ bitrate=125_000,
+ tseg1=13,
+ tseg2=2,
+ sjw=1,
+ )
+ bus = can.Bus(
+ channel=0,
+ serial=_find_virtual_can_serial(),
+ interface="vector",
+ timing=timing,
+ )
+ assert isinstance(bus, canlib.VectorBus)
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert xl_channel_config.busParams.data.can.bitRate == 125_000
+ assert xl_channel_config.busParams.data.can.sjw == 1
+ assert xl_channel_config.busParams.data.can.tseg1 == 13
+ assert xl_channel_config.busParams.data.can.tseg2 == 2
+
+ bus.shutdown()
+
+
+def test_bus_creation_timingfd_mocked(mock_xldriver) -> None:
+ timing = can.BitTimingFd.from_bitrate_and_segments(
+ f_clock=80_000_000,
+ nom_bitrate=500_000,
+ nom_tseg1=68,
+ nom_tseg2=11,
+ nom_sjw=10,
+ data_bitrate=2_000_000,
+ data_tseg1=10,
+ data_tseg2=9,
+ data_sjw=8,
+ )
+ bus = can.Bus(
+ channel=0,
+ interface="vector",
+ timing=timing,
+ _testing=True,
+ )
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert (
+ xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value
+ )
+
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called()
+
+ xlCanFdSetConfiguration_args = (
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0]
+ )
+ canFdConf = xlCanFdSetConfiguration_args[2]
+ assert canFdConf.arbitrationBitRate == 500_000
+ assert canFdConf.dataBitRate == 2_000_000
+ assert canFdConf.sjwAbr == 10
+ assert canFdConf.tseg1Abr == 68
+ assert canFdConf.tseg2Abr == 11
+ assert canFdConf.sjwDbr == 8
+ assert canFdConf.tseg1Dbr == 10
+ assert canFdConf.tseg2Dbr == 9
+
+
+@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
+def test_bus_creation_timingfd() -> None:
+ timing = can.BitTimingFd.from_bitrate_and_segments(
+ f_clock=80_000_000,
+ nom_bitrate=500_000,
+ nom_tseg1=68,
+ nom_tseg2=11,
+ nom_sjw=10,
+ data_bitrate=2_000_000,
+ data_tseg1=10,
+ data_tseg2=9,
+ data_sjw=8,
+ )
+ bus = can.Bus(
+ channel=0,
+ serial=_find_virtual_can_serial(),
+ interface="vector",
+ timing=timing,
+ )
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert (
+ xl_channel_config.interfaceVersion
+ == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4
+ )
+ assert (
+ xl_channel_config.busParams.data.canFD.canOpMode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
+ )
+ assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000
+ assert xl_channel_config.busParams.data.canFD.sjwAbr == 10
+ assert xl_channel_config.busParams.data.canFD.tseg1Abr == 68
+ assert xl_channel_config.busParams.data.canFD.tseg2Abr == 11
+ assert xl_channel_config.busParams.data.canFD.sjwDbr == 8
+ assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 10
+ assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 9
+ assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000
+
+ bus.shutdown()
+
+
def test_send_mocked(mock_xldriver) -> None:
bus = can.Bus(channel=0, interface="vector", _testing=True)
msg = can.Message(
From 356f6f9fde3c99d83a9b174d53f092e99a83a45b Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 28 Jan 2023 16:02:25 +0100
Subject: [PATCH 232/475] Add code example to BitTiming docs (#1515)
* add copy button
* make bittiming classes hashable
* add code example to show possible bit timings
* fix tests
* rename variable
---
can/bit_timing.py | 16 +++++++++++-----
doc/bit_timing.rst | 30 +++++++++++++++++++++++++++++-
doc/conf.py | 1 +
doc/doc-requirements.txt | 1 +
test/test_bit_timing.py | 27 ++++++++++++++++++++++-----
5 files changed, 64 insertions(+), 11 deletions(-)
diff --git a/can/bit_timing.py b/can/bit_timing.py
index 2dc78064b..4fada145b 100644
--- a/can/bit_timing.py
+++ b/can/bit_timing.py
@@ -395,14 +395,14 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
def __str__(self) -> str:
segments = [
- f"BR {self.bitrate} bit/s",
+ f"BR: {self.bitrate:_} bit/s",
f"SP: {self.sample_point:.2f}%",
f"BRP: {self.brp}",
f"TSEG1: {self.tseg1}",
f"TSEG2: {self.tseg2}",
f"SJW: {self.sjw}",
f"BTR: {self.btr0:02X}{self.btr1:02X}h",
- f"f_clock: {self.f_clock / 1e6:.0f}MHz",
+ f"CLK: {self.f_clock / 1e6:.0f}MHz",
]
return ", ".join(segments)
@@ -425,6 +425,9 @@ def __eq__(self, other: object) -> bool:
return self._data == other._data
+ def __hash__(self) -> int:
+ return tuple(self._data.values()).__hash__()
+
class BitTimingFd(Mapping):
"""Representation of a bit timing configuration for a CAN FD bus.
@@ -999,19 +1002,19 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd":
def __str__(self) -> str:
segments = [
- f"NBR: {self.nom_bitrate} bit/s",
+ f"NBR: {self.nom_bitrate:_} bit/s",
f"NSP: {self.nom_sample_point:.2f}%",
f"NBRP: {self.nom_brp}",
f"NTSEG1: {self.nom_tseg1}",
f"NTSEG2: {self.nom_tseg2}",
f"NSJW: {self.nom_sjw}",
- f"DBR: {self.data_bitrate} bit/s",
+ f"DBR: {self.data_bitrate:_} bit/s",
f"DSP: {self.data_sample_point:.2f}%",
f"DBRP: {self.data_brp}",
f"DTSEG1: {self.data_tseg1}",
f"DTSEG2: {self.data_tseg2}",
f"DSJW: {self.data_sjw}",
- f"f_clock: {self.f_clock / 1e6:.0f}MHz",
+ f"CLK: {self.f_clock / 1e6:.0f}MHz",
]
return ", ".join(segments)
@@ -1034,6 +1037,9 @@ def __eq__(self, other: object) -> bool:
return self._data == other._data
+ def __hash__(self) -> int:
+ return tuple(self._data.values()).__hash__()
+
def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float:
"""Arbitration phase - resynchronization"""
diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst
index b48a133b8..73005a3c6 100644
--- a/doc/bit_timing.rst
+++ b/doc/bit_timing.rst
@@ -63,10 +63,38 @@ to specify custom bit timings. The :class:`~can.BitTiming` and
:class:`~can.BitTimingFd` classes can be used for this purpose to specify
bit timings in a relatively interface agnostic manner.
+:class:`~can.BitTiming` or :class:`~can.BitTimingFd` can also help you to
+produce an overview of possible bit timings for your desired bit rate:
+
+ >>> import contextlib
+ >>> import can
+ ...
+ >>> timings = set()
+ >>> for sample_point in range(50, 100):
+ ... with contextlib.suppress(ValueError):
+ ... timings.add(
+ ... can.BitTiming.from_sample_point(
+ ... f_clock=8_000_000,
+ ... bitrate=250_000,
+ ... sample_point=sample_point,
+ ... )
+ ... )
+ ...
+ >>> for timing in sorted(timings, key=lambda x: x.sample_point):
+ ... print(timing)
+ BR: 250_000 bit/s, SP: 50.00%, BRP: 2, TSEG1: 7, TSEG2: 8, SJW: 4, BTR: C176h, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 56.25%, BRP: 2, TSEG1: 8, TSEG2: 7, SJW: 4, BTR: C167h, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 62.50%, BRP: 2, TSEG1: 9, TSEG2: 6, SJW: 4, BTR: C158h, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 68.75%, BRP: 2, TSEG1: 10, TSEG2: 5, SJW: 4, BTR: C149h, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 75.00%, BRP: 2, TSEG1: 11, TSEG2: 4, SJW: 4, BTR: C13Ah, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 81.25%, BRP: 2, TSEG1: 12, TSEG2: 3, SJW: 3, BTR: 812Bh, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 87.50%, BRP: 2, TSEG1: 13, TSEG2: 2, SJW: 2, BTR: 411Ch, CLK: 8MHz
+ BR: 250_000 bit/s, SP: 93.75%, BRP: 2, TSEG1: 14, TSEG2: 1, SJW: 1, BTR: 010Dh, CLK: 8MHz
+
+
It is possible to specify CAN 2.0 bit timings
using the config file:
-
.. code-block:: none
[default]
diff --git a/doc/conf.py b/doc/conf.py
index 6ba661a30..cea93440d 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -49,6 +49,7 @@
"sphinx.ext.graphviz",
"sphinxcontrib.programoutput",
"sphinx_inline_tabs",
+ "sphinx_copybutton",
]
# Now, you can use the alias name as a new role, e.g. :issue:`123`.
diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt
index c61b55683..9a01cf589 100644
--- a/doc/doc-requirements.txt
+++ b/doc/doc-requirements.txt
@@ -1,4 +1,5 @@
sphinx>=5.2.3
sphinxcontrib-programoutput
sphinx-inline-tabs
+sphinx-copybutton
furo
diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py
index 669308f9d..a0d4e03a5 100644
--- a/test/test_bit_timing.py
+++ b/test/test_bit_timing.py
@@ -271,8 +271,8 @@ def test_equality():
def test_string_representation():
timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
assert str(timing) == (
- "BR 1000000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, "
- "BTR: 0014h, f_clock: 8MHz"
+ "BR: 1_000_000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, "
+ "BTR: 0014h, CLK: 8MHz"
)
fd_timing = can.BitTimingFd(
@@ -287,9 +287,9 @@ def test_string_representation():
data_sjw=10,
)
assert str(fd_timing) == (
- "NBR: 500000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, "
- "DBR: 2000000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, "
- "f_clock: 80MHz"
+ "NBR: 500_000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, "
+ "DBR: 2_000_000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, "
+ "CLK: 80MHz"
)
@@ -316,6 +316,23 @@ def test_repr():
)
+def test_hash():
+ _timings = {
+ can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1),
+ can.BitTimingFd(
+ f_clock=80_000_000,
+ nom_brp=1,
+ nom_tseg1=119,
+ nom_tseg2=40,
+ nom_sjw=40,
+ data_brp=1,
+ data_tseg1=29,
+ data_tseg2=10,
+ data_sjw=10,
+ ),
+ }
+
+
def test_mapping():
timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1)
timing_dict = dict(timing)
From e96abcf42996d73c30de003b576428e48e27183e Mon Sep 17 00:00:00 2001
From: Faisal Shah <37458679+faisal-shah@users.noreply.github.com>
Date: Sat, 28 Jan 2023 09:58:56 -0600
Subject: [PATCH 233/475] Socketcand ext ID bug fixes, and implement
channel{_info} and Message attributes (#1508)
* Handle extended arbitration id properly
socketcand uses the length of the arbitration id field to indicate
whether a frame is using extended id or not. 3 characters for standard,
8 for extended.
Prior to this fix, the arbitration id would be truncated to the lower 11
bits, or at times even garbage.
* Add is_rx attribute
* Populate channel{_info} and Message attributes
---
can/interfaces/socketcand/socketcand.py | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py
index 28f0c700f..32b9a0edf 100644
--- a/can/interfaces/socketcand/socketcand.py
+++ b/can/interfaces/socketcand/socketcand.py
@@ -27,11 +27,17 @@ def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message:
frame_string = ascii_msg[8:-2]
parts = frame_string.split(" ", 3)
can_id, timestamp = int(parts[0], 16), float(parts[1])
+ is_ext = len(parts[0]) != 3
data = bytearray.fromhex(parts[2])
can_dlc = len(data)
can_message = can.Message(
- timestamp=timestamp, arbitration_id=can_id, data=data, dlc=can_dlc
+ timestamp=timestamp,
+ arbitration_id=can_id,
+ data=data,
+ dlc=can_dlc,
+ is_extended_id=is_ext,
+ is_rx=True,
)
return can_message
@@ -40,11 +46,15 @@ def convert_can_message_to_ascii_message(can_message: can.Message) -> str:
# Note: socketcan bus adds extended flag, remote_frame_flag & error_flag to id
# not sure if that is necessary here
can_id = can_message.arbitration_id
+ if can_message.is_extended_id:
+ can_id_string = f"{(can_id&0x1FFFFFFF):08X}"
+ else:
+ can_id_string = f"{(can_id&0x7FF):03X}"
# Note: seems like we cannot add CANFD_BRS (bitrate_switch) and CANFD_ESI (error_state_indicator) flags
data = can_message.data
length = can_message.dlc
bytes_string = " ".join(f"{x:x}" for x in data[0:length])
- return f"< send {can_id:X} {length:X} {bytes_string} >"
+ return f"< send {can_id_string} {length:X} {bytes_string} >"
def connect_to_server(s, host, port):
@@ -70,6 +80,8 @@ def __init__(self, channel, host, port, can_filters=None, **kwargs):
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__message_buffer = deque()
self.__receive_buffer = "" # i know string is not the most efficient here
+ self.channel = channel
+ self.channel_info = f"socketcand on {channel}@{host}:{port}"
connect_to_server(self.__socket, self.__host, self.__port)
self._expect_msg("< hi >")
@@ -139,6 +151,7 @@ def _recv_internal(self, timeout):
if parsed_can_message is None:
log.warning(f"Invalid Frame: {single_message}")
else:
+ parsed_can_message.channel = self.channel
self.__message_buffer.append(parsed_can_message)
buffer_view = buffer_view[end + 1 :]
From 047cbe44d46105d3433f4041edf4ef4a30b3dfd1 Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Mon, 30 Jan 2023 11:01:58 +0100
Subject: [PATCH 234/475] Align ixxat interface's `shutdown()` with
`can.BusABC` typing
---
can/interfaces/ixxat/canlib.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py
index 1e4055f89..cb7131c57 100644
--- a/can/interfaces/ixxat/canlib.py
+++ b/can/interfaces/ixxat/canlib.py
@@ -146,8 +146,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
def _send_periodic_internal(self, msgs, period, duration=None):
return self.bus._send_periodic_internal(msgs, period, duration)
- def shutdown(self):
- return self.bus.shutdown()
+ def shutdown(self) -> None:
+ self.bus.shutdown()
@property
def state(self) -> BusState:
From 73593762d946ad40e83a5b59f82eea4f4757a5de Mon Sep 17 00:00:00 2001
From: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Date: Mon, 30 Jan 2023 14:51:04 +0100
Subject: [PATCH 235/475] Cleanup ixxat interface code (#1517)
---
can/interfaces/ixxat/canlib.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py
index cb7131c57..a20e4f59b 100644
--- a/can/interfaces/ixxat/canlib.py
+++ b/can/interfaces/ixxat/canlib.py
@@ -155,6 +155,3 @@ def state(self) -> BusState:
Return the current state of the hardware
"""
return self.bus.state
-
-
-# ~class IXXATBus(BusABC): ---------------------------------------------------
From 9cd7b49b7bf8c33cf0cdea0ba15cde7cf0ef2fa3 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 2 Feb 2023 22:06:55 +0100
Subject: [PATCH 236/475] print warnings
---
test/test_util.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/test/test_util.py b/test/test_util.py
index 88349d974..b6c261602 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -245,7 +245,9 @@ def test_adjust_timing_fd(self):
new_timing = check_or_adjust_timing_clock(
timing, valid_clocks=[8_000, 80_000_000]
)
- assert len(record) == 1
+ assert len(record) == 1, "; ".join(
+ [record[i].message.args[0] for i in range(len(record))]
+ ) # print all warnings, if more than one warning is present
assert (
record[0].message.args[0]
== "Adjusted f_clock in BitTimingFd from 160000000 to 80000000"
From 8b6bfa9c9591fad2ab4f860cb6733c28aaf43f56 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 4 Feb 2023 01:02:22 +0100
Subject: [PATCH 237/475] Vector: refactor bit timing setup (#1516)
* add xlCanSetChannelParamsC200
* refactor VectorBus
* move check into separate method
* improve error message
* use Optional instead of |
---
can/interfaces/vector/canlib.py | 294 ++++++++++++++----------------
can/interfaces/vector/xldriver.py | 12 +-
test/test_vector.py | 126 ++++++++-----
3 files changed, 221 insertions(+), 211 deletions(-)
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index 287f45437..d53b1418d 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -120,9 +120,10 @@ def __init__(
:param timing:
An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd`
to specify the bit timing parameters for the VectorBus interface. The
- `f_clock` value of the timing instance must be set to 16.000.000 (16MHz)
- for standard CAN or 80.000.000 (80MHz) for CAN FD. If this parameter is provided,
- it takes precedence over all other timing-related parameters.
+ `f_clock` value of the timing instance must be set to 8_000_000 (8MHz)
+ or 16_000_000 (16MHz) for CAN 2.0 or 80_000_000 (80MHz) for CAN FD.
+ If this parameter is provided, it takes precedence over all other
+ timing-related parameters.
Otherwise, the bit timing can be specified using the following parameters:
`bitrate` for standard CAN or `fd`, `data_bitrate`, `sjw_abr`, `tseg1_abr`,
`tseg2_abr`, `sjw_dbr`, `tseg1_dbr`, and `tseg2_dbr` for CAN FD.
@@ -252,42 +253,34 @@ def __init__(
# set CAN settings
for channel in self.channels:
if isinstance(timing, BitTiming):
- timing = check_or_adjust_timing_clock(timing, [16_000_000])
- self._set_bitrate_can(
+ timing = check_or_adjust_timing_clock(timing, [16_000_000, 8_000_000])
+ self._set_bit_timing(
channel=channel,
- bitrate=timing.bitrate,
- sjw=timing.sjw,
- tseg1=timing.tseg1,
- tseg2=timing.tseg2,
- sam=timing.nof_samples,
+ timing=timing,
)
elif isinstance(timing, BitTimingFd):
timing = check_or_adjust_timing_clock(timing, [80_000_000])
- self._set_bitrate_canfd(
+ self._set_bit_timing_fd(
channel=channel,
- bitrate=timing.nom_bitrate,
- data_bitrate=timing.data_bitrate,
- sjw_abr=timing.nom_sjw,
- tseg1_abr=timing.nom_tseg1,
- tseg2_abr=timing.nom_tseg2,
- sjw_dbr=timing.data_sjw,
- tseg1_dbr=timing.data_tseg1,
- tseg2_dbr=timing.data_tseg2,
+ timing=timing,
)
elif fd:
- self._set_bitrate_canfd(
+ self._set_bit_timing_fd(
channel=channel,
- bitrate=bitrate,
- data_bitrate=data_bitrate,
- sjw_abr=sjw_abr,
- tseg1_abr=tseg1_abr,
- tseg2_abr=tseg2_abr,
- sjw_dbr=sjw_dbr,
- tseg1_dbr=tseg1_dbr,
- tseg2_dbr=tseg2_dbr,
+ timing=BitTimingFd.from_bitrate_and_segments(
+ f_clock=80_000_000,
+ nom_bitrate=bitrate or 500_000,
+ nom_tseg1=tseg1_abr,
+ nom_tseg2=tseg2_abr,
+ nom_sjw=sjw_abr,
+ data_bitrate=data_bitrate or bitrate or 500_000,
+ data_tseg1=tseg1_dbr,
+ data_tseg2=tseg2_dbr,
+ data_sjw=sjw_dbr,
+ ),
)
elif bitrate:
- self._set_bitrate_can(channel=channel, bitrate=bitrate)
+ self._set_bitrate(channel=channel, bitrate=bitrate)
# Enable/disable TX receipts
tx_receipts = 1 if receive_own_messages else 0
@@ -404,30 +397,44 @@ def _read_bus_params(self, channel: int) -> "VectorBusParams":
f"Channel configuration for channel {channel} not found."
)
- def _set_bitrate_can(
- self,
- channel: int,
- bitrate: int,
- sjw: Optional[int] = None,
- tseg1: Optional[int] = None,
- tseg2: Optional[int] = None,
- sam: int = 1,
- ) -> None:
- kwargs = [sjw, tseg1, tseg2]
- if any(kwargs) and not all(kwargs):
- raise ValueError(
- f"Either all of sjw, tseg1, tseg2 must be set or none of them."
+ def _set_bitrate(self, channel: int, bitrate: int) -> None:
+ # set parameters if channel has init access
+ if self._has_init_access(channel):
+ self.xldriver.xlCanSetChannelBitrate(
+ self.port_handle,
+ self.channel_masks[channel],
+ bitrate,
)
+ LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate)
+ if not self.__testing:
+ self._check_can_settings(
+ channel=channel,
+ bitrate=bitrate,
+ )
+
+ def _set_bit_timing(self, channel: int, timing: BitTiming) -> None:
# set parameters if channel has init access
if self._has_init_access(channel):
- if any(kwargs):
+ if timing.f_clock == 8_000_000:
+ self.xldriver.xlCanSetChannelParamsC200(
+ self.port_handle,
+ self.channel_masks[channel],
+ timing.btr0,
+ timing.btr1,
+ )
+ LOG.info(
+ "xlCanSetChannelParamsC200: BTR0=%#02x, BTR1=%#02x",
+ timing.btr0,
+ timing.btr1,
+ )
+ elif timing.f_clock == 16_000_000:
chip_params = xlclass.XLchipParams()
- chip_params.bitRate = bitrate
- chip_params.sjw = sjw
- chip_params.tseg1 = tseg1
- chip_params.tseg2 = tseg2
- chip_params.sam = sam
+ chip_params.bitRate = timing.bitrate
+ chip_params.sjw = timing.sjw
+ chip_params.tseg1 = timing.tseg1
+ chip_params.tseg2 = timing.tseg2
+ chip_params.sam = timing.nof_samples
self.xldriver.xlCanSetChannelParams(
self.port_handle,
self.channel_masks[channel],
@@ -441,94 +448,33 @@ def _set_bitrate_can(
chip_params.tseg2,
)
else:
- self.xldriver.xlCanSetChannelBitrate(
- self.port_handle,
- self.channel_masks[channel],
- bitrate,
+ raise CanInitializationError(
+ f"timing.f_clock must be 8_000_000 or 16_000_000 (is {timing.f_clock})"
)
- LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate)
-
- if self.__testing:
- return
- # Compare requested CAN settings to active settings
- bus_params = self._read_bus_params(channel)
- settings_acceptable = True
-
- # check bus type
- settings_acceptable &= (
- bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN
- )
-
- # check CAN operation mode. For CANcaseXL can_op_mode remains 0
- if bus_params.can.can_op_mode != 0:
- settings_acceptable &= bool(
- bus_params.can.can_op_mode
- & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20
- )
-
- # check bitrate
- settings_acceptable &= abs(bus_params.can.bitrate - bitrate) < bitrate / 256
-
- # check sample point
- if all(kwargs):
- requested_sample_point = (
- 100
- * (1 + tseg1) # type: ignore[operator]
- / (1 + tseg1 + tseg2) # type: ignore[operator]
- )
- actual_sample_point = (
- 100
- * (1 + bus_params.can.tseg1)
- / (1 + bus_params.can.tseg1 + bus_params.can.tseg2)
- )
- settings_acceptable &= (
- abs(actual_sample_point - requested_sample_point)
- < 1.0 # 1 percent threshold
- )
-
- if not settings_acceptable:
- active_settings = ", ".join(
- [
- f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name
- for key, val in bus_params.can._asdict().items()
- ]
- )
- raise CanInitializationError(
- f"The requested CAN settings could not be set for channel {channel}. "
- f"Another application might have set incompatible settings. "
- f"These are the currently active settings: {active_settings}"
+ if not self.__testing:
+ self._check_can_settings(
+ channel=channel,
+ bitrate=timing.bitrate,
+ sample_point=timing.sample_point,
)
- def _set_bitrate_canfd(
+ def _set_bit_timing_fd(
self,
channel: int,
- bitrate: Optional[int] = None,
- data_bitrate: Optional[int] = None,
- sjw_abr: int = 2,
- tseg1_abr: int = 6,
- tseg2_abr: int = 3,
- sjw_dbr: int = 2,
- tseg1_dbr: int = 6,
- tseg2_dbr: int = 3,
+ timing: BitTimingFd,
) -> None:
# set parameters if channel has init access
if self._has_init_access(channel):
canfd_conf = xlclass.XLcanFdConf()
- if bitrate:
- canfd_conf.arbitrationBitRate = int(bitrate)
- else:
- canfd_conf.arbitrationBitRate = 500_000
- canfd_conf.sjwAbr = int(sjw_abr)
- canfd_conf.tseg1Abr = int(tseg1_abr)
- canfd_conf.tseg2Abr = int(tseg2_abr)
- if data_bitrate:
- canfd_conf.dataBitRate = int(data_bitrate)
- else:
- canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate)
- canfd_conf.sjwDbr = int(sjw_dbr)
- canfd_conf.tseg1Dbr = int(tseg1_dbr)
- canfd_conf.tseg2Dbr = int(tseg2_dbr)
+ canfd_conf.arbitrationBitRate = timing.nom_bitrate
+ canfd_conf.sjwAbr = timing.nom_sjw
+ canfd_conf.tseg1Abr = timing.nom_tseg1
+ canfd_conf.tseg2Abr = timing.nom_tseg2
+ canfd_conf.dataBitRate = timing.data_bitrate
+ canfd_conf.sjwDbr = timing.data_sjw
+ canfd_conf.tseg1Dbr = timing.data_tseg1
+ canfd_conf.tseg2Dbr = timing.data_tseg2
self.xldriver.xlCanFdSetConfiguration(
self.port_handle, self.channel_masks[channel], canfd_conf
)
@@ -550,11 +496,29 @@ def _set_bitrate_canfd(
canfd_conf.tseg2Dbr,
)
- if self.__testing:
- return
+ if not self.__testing:
+ self._check_can_settings(
+ channel=channel,
+ bitrate=timing.nom_bitrate,
+ sample_point=timing.nom_sample_point,
+ fd=True,
+ data_bitrate=timing.data_bitrate,
+ data_sample_point=timing.data_sample_point,
+ )
- # Compare requested CAN settings to active settings
+ def _check_can_settings(
+ self,
+ channel: int,
+ bitrate: int,
+ sample_point: Optional[float] = None,
+ fd: bool = False,
+ data_bitrate: Optional[int] = None,
+ data_sample_point: Optional[float] = None,
+ ) -> None:
+ """Compare requested CAN settings to active settings in driver."""
bus_params = self._read_bus_params(channel)
+ # use canfd even if fd==False, bus_params.can and bus_params.canfd are a C union
+ bus_params_data = bus_params.canfd
settings_acceptable = True
# check bus type
@@ -563,60 +527,68 @@ def _set_bitrate_canfd(
)
# check CAN operation mode
- settings_acceptable &= bool(
- bus_params.canfd.can_op_mode
- & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
- )
+ if fd:
+ settings_acceptable &= bool(
+ bus_params_data.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
+ )
+ elif bus_params_data.can_op_mode != 0: # can_op_mode is always 0 for cancaseXL
+ settings_acceptable &= bool(
+ bus_params_data.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20
+ )
# check bitrates
if bitrate:
settings_acceptable &= (
- abs(bus_params.canfd.bitrate - bitrate) < bitrate / 256
+ abs(bus_params_data.bitrate - bitrate) < bitrate / 256
)
- if data_bitrate:
+ if fd and data_bitrate:
settings_acceptable &= (
- abs(bus_params.canfd.data_bitrate - data_bitrate) < data_bitrate / 256
+ abs(bus_params_data.data_bitrate - data_bitrate) < data_bitrate / 256
)
# check sample points
- if bitrate:
- requested_nom_sample_point = (
- 100 * (1 + tseg1_abr) / (1 + tseg1_abr + tseg2_abr)
- )
- actual_nom_sample_point = (
+ if sample_point:
+ nom_sample_point_act = (
100
- * (1 + bus_params.canfd.tseg1_abr)
- / (1 + bus_params.canfd.tseg1_abr + bus_params.canfd.tseg2_abr)
+ * (1 + bus_params_data.tseg1_abr)
+ / (1 + bus_params_data.tseg1_abr + bus_params_data.tseg2_abr)
)
settings_acceptable &= (
- abs(actual_nom_sample_point - requested_nom_sample_point)
- < 1.0 # 1 percent threshold
- )
- if data_bitrate:
- requested_data_sample_point = (
- 100 * (1 + tseg1_dbr) / (1 + tseg1_dbr + tseg2_dbr)
+ abs(nom_sample_point_act - sample_point) < 2.0 # 2 percent tolerance
)
- actual_data_sample_point = (
+ if fd and data_sample_point:
+ data_sample_point_act = (
100
- * (1 + bus_params.canfd.tseg1_dbr)
- / (1 + bus_params.canfd.tseg1_dbr + bus_params.canfd.tseg2_dbr)
+ * (1 + bus_params_data.tseg1_dbr)
+ / (1 + bus_params_data.tseg1_dbr + bus_params_data.tseg2_dbr)
)
settings_acceptable &= (
- abs(actual_data_sample_point - requested_data_sample_point)
- < 1.0 # 1 percent threshold
+ abs(data_sample_point_act - data_sample_point)
+ < 2.0 # 2 percent tolerance
)
if not settings_acceptable:
- active_settings = ", ".join(
- [
- f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name
- for key, val in bus_params.canfd._asdict().items()
- ]
+ # The error message depends on the currently active CAN settings.
+ # If the active operation mode is CAN FD, show the active CAN FD timings,
+ # otherwise show CAN 2.0 timings.
+ if bool(
+ bus_params_data.can_op_mode
+ & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
+ ):
+ active_settings = bus_params.canfd._asdict()
+ active_settings["can_op_mode"] = "CAN FD"
+ else:
+ active_settings = bus_params.can._asdict()
+ active_settings["can_op_mode"] = "CAN 2.0"
+ settings_string = ", ".join(
+ [f"{key}: {val}" for key, val in active_settings.items()]
)
raise CanInitializationError(
- f"The requested CAN FD settings could not be set for channel {channel}. "
+ f"The requested settings could not be set for channel {channel}. "
f"Another application might have set incompatible settings. "
- f"These are the currently active settings: {active_settings}."
+ f"These are the currently active settings: {settings_string}."
)
def _apply_filters(self, filters: Optional[CanFilters]) -> None:
diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py
index 8df39e9dc..29791e32f 100644
--- a/can/interfaces/vector/xldriver.py
+++ b/can/interfaces/vector/xldriver.py
@@ -201,7 +201,17 @@ def check_status_initialization(result, function, arguments):
ctypes.POINTER(xlclass.XLchipParams),
]
xlCanSetChannelParams.restype = xlclass.XLstatus
-xlCanSetChannelParams.errcheck = check_status_operation
+xlCanSetChannelParams.errcheck = check_status_initialization
+
+xlCanSetChannelParamsC200 = _xlapi_dll.xlCanSetChannelParamsC200
+xlCanSetChannelParamsC200.argtypes = [
+ xlclass.XLportHandle,
+ xlclass.XLaccess,
+ ctypes.c_ubyte,
+ ctypes.c_ubyte,
+]
+xlCanSetChannelParams.restype = xlclass.XLstatus
+xlCanSetChannelParams.errcheck = check_status_initialization
xlCanTransmit = _xlapi_dll.xlCanTransmit
xlCanTransmit.argtypes = [
diff --git a/test/test_vector.py b/test/test_vector.py
index fd95cef8a..7694b31aa 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -193,12 +193,12 @@ def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None:
fd=True,
bitrate=500_000,
data_bitrate=2_000_000,
- sjw_abr=10,
- tseg1_abr=11,
- tseg2_abr=12,
- sjw_dbr=13,
- tseg1_dbr=14,
- tseg2_dbr=15,
+ sjw_abr=16,
+ tseg1_abr=127,
+ tseg2_abr=32,
+ sjw_dbr=6,
+ tseg1_dbr=27,
+ tseg2_dbr=12,
_testing=True,
)
assert isinstance(bus, canlib.VectorBus)
@@ -222,12 +222,12 @@ def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None:
canFdConf = xlCanFdSetConfiguration_args[2]
assert canFdConf.arbitrationBitRate == 500000
assert canFdConf.dataBitRate == 2000000
- assert canFdConf.sjwAbr == 10
- assert canFdConf.tseg1Abr == 11
- assert canFdConf.tseg2Abr == 12
- assert canFdConf.sjwDbr == 13
- assert canFdConf.tseg1Dbr == 14
- assert canFdConf.tseg2Dbr == 15
+ assert canFdConf.sjwAbr == 16
+ assert canFdConf.tseg1Abr == 127
+ assert canFdConf.tseg2Abr == 32
+ assert canFdConf.sjwDbr == 6
+ assert canFdConf.tseg1Dbr == 27
+ assert canFdConf.tseg2Dbr == 12
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
@@ -239,12 +239,12 @@ def test_bus_creation_fd_bitrate_timings() -> None:
fd=True,
bitrate=500_000,
data_bitrate=2_000_000,
- sjw_abr=10,
- tseg1_abr=11,
- tseg2_abr=12,
- sjw_dbr=13,
- tseg1_dbr=14,
- tseg2_dbr=15,
+ sjw_abr=16,
+ tseg1_abr=127,
+ tseg2_abr=32,
+ sjw_dbr=6,
+ tseg1_dbr=27,
+ tseg2_dbr=12,
)
xl_channel_config = _find_xl_channel_config(
@@ -259,18 +259,45 @@ def test_bus_creation_fd_bitrate_timings() -> None:
& xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD
)
assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000
- assert xl_channel_config.busParams.data.canFD.sjwAbr == 10
- assert xl_channel_config.busParams.data.canFD.tseg1Abr == 11
- assert xl_channel_config.busParams.data.canFD.tseg2Abr == 12
- assert xl_channel_config.busParams.data.canFD.sjwDbr == 13
- assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 14
- assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 15
+ assert xl_channel_config.busParams.data.canFD.sjwAbr == 16
+ assert xl_channel_config.busParams.data.canFD.tseg1Abr == 127
+ assert xl_channel_config.busParams.data.canFD.tseg2Abr == 32
+ assert xl_channel_config.busParams.data.canFD.sjwDbr == 6
+ assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 27
+ assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 12
assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000
bus.shutdown()
-def test_bus_creation_timing_mocked(mock_xldriver) -> None:
+def test_bus_creation_timing_8mhz_mocked(mock_xldriver) -> None:
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=8_000_000,
+ bitrate=125_000,
+ tseg1=13,
+ tseg2=2,
+ sjw=1,
+ )
+ bus = can.Bus(channel=0, interface="vector", timing=timing, _testing=True)
+ assert isinstance(bus, canlib.VectorBus)
+ can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called()
+ can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called()
+
+ can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called()
+ xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0]
+ assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value
+ assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value
+
+ can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called()
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelParamsC200.assert_called()
+ btr0, btr1 = (
+ can.interfaces.vector.canlib.xldriver.xlCanSetChannelParamsC200.call_args[0]
+ )[2:]
+ assert btr0 == timing.btr0
+ assert btr1 == timing.btr1
+
+
+def test_bus_creation_timing_16mhz_mocked(mock_xldriver) -> None:
timing = can.BitTiming.from_bitrate_and_segments(
f_clock=16_000_000,
bitrate=125_000,
@@ -302,30 +329,31 @@ def test_bus_creation_timing_mocked(mock_xldriver) -> None:
@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable")
def test_bus_creation_timing() -> None:
- timing = can.BitTiming.from_bitrate_and_segments(
- f_clock=16_000_000,
- bitrate=125_000,
- tseg1=13,
- tseg2=2,
- sjw=1,
- )
- bus = can.Bus(
- channel=0,
- serial=_find_virtual_can_serial(),
- interface="vector",
- timing=timing,
- )
- assert isinstance(bus, canlib.VectorBus)
-
- xl_channel_config = _find_xl_channel_config(
- serial=_find_virtual_can_serial(), channel=0
- )
- assert xl_channel_config.busParams.data.can.bitRate == 125_000
- assert xl_channel_config.busParams.data.can.sjw == 1
- assert xl_channel_config.busParams.data.can.tseg1 == 13
- assert xl_channel_config.busParams.data.can.tseg2 == 2
-
- bus.shutdown()
+ for f_clock in [8_000_000, 16_000_000]:
+ timing = can.BitTiming.from_bitrate_and_segments(
+ f_clock=f_clock,
+ bitrate=125_000,
+ tseg1=13,
+ tseg2=2,
+ sjw=1,
+ )
+ bus = can.Bus(
+ channel=0,
+ serial=_find_virtual_can_serial(),
+ interface="vector",
+ timing=timing,
+ )
+ assert isinstance(bus, canlib.VectorBus)
+
+ xl_channel_config = _find_xl_channel_config(
+ serial=_find_virtual_can_serial(), channel=0
+ )
+ assert xl_channel_config.busParams.data.can.bitRate == 125_000
+ assert xl_channel_config.busParams.data.can.sjw == 1
+ assert xl_channel_config.busParams.data.can.tseg1 == 13
+ assert xl_channel_config.busParams.data.can.tseg2 == 2
+
+ bus.shutdown()
def test_bus_creation_timingfd_mocked(mock_xldriver) -> None:
From 814051e489f8008d475c6b6acaaabde2b8842da9 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 4 Feb 2023 01:06:34 +0100
Subject: [PATCH 238/475] Add BitTiming/BitTimingFd support to NiXNETcanBus
(#1520)
---
can/interfaces/nixnet.py | 295 +++++++++++++++++++++++++--------------
setup.py | 2 +-
2 files changed, 188 insertions(+), 109 deletions(-)
diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py
index e304fbc1f..6c3e63697 100644
--- a/can/interfaces/nixnet.py
+++ b/can/interfaces/nixnet.py
@@ -10,21 +10,28 @@
import logging
import os
+import time
+from queue import SimpleQueue
from types import ModuleType
-from typing import Optional
+from typing import Optional, List, Union, Tuple, Any
-from can import BusABC, Message
-from ..exceptions import (
+import can.typechecking
+from can import BusABC, Message, BitTiming, BitTimingFd
+from can.exceptions import (
CanInitializationError,
CanOperationError,
CanInterfaceNotImplementedError,
)
+from can.util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
nixnet: Optional[ModuleType] = None
try:
import nixnet # type: ignore
+ import nixnet.constants # type: ignore
+ import nixnet.system # type: ignore
+ import nixnet.types # type: ignore
except Exception as exc:
logger.warning("Could not import nixnet: %s", exc)
@@ -34,18 +41,25 @@ class NiXNETcanBus(BusABC):
The CAN Bus implemented for the NI-XNET interface.
"""
+ @deprecated_args_alias(
+ deprecation_start="4.2.0",
+ deprecation_end="5.0.0",
+ brs=None,
+ log_errors=None,
+ )
def __init__(
self,
- channel,
- can_filters=None,
- bitrate=None,
- fd=False,
- fd_bitrate=None,
- brs=False,
- can_termination=False,
- log_errors=True,
- **kwargs,
- ):
+ channel: str = "CAN1",
+ bitrate: int = 500_000,
+ timing: Optional[Union[BitTiming, BitTimingFd]] = None,
+ can_filters: Optional[can.typechecking.CanFilters] = None,
+ receive_own_messages: bool = False,
+ can_termination: bool = False,
+ fd: bool = False,
+ fd_bitrate: Optional[int] = None,
+ poll_interval: float = 0.001,
+ **kwargs: Any,
+ ) -> None:
"""
:param str channel:
Name of the object to open (e.g. 'CAN0')
@@ -53,13 +67,21 @@ def __init__(
:param int bitrate:
Bitrate in bits/s
+ :param timing:
+ Optional :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance
+ to use for custom bit timing setting. The `f_clock` value of the timing
+ instance must be set to 40_000_000 (40MHz).
+ If this parameter is provided, it takes precedence over all other
+ timing-related parameters like `bitrate`, `fd_bitrate` and `fd`.
+
:param list can_filters:
See :meth:`can.BusABC.set_filters`.
- :param bool log_errors:
- If True, communication errors will appear as CAN messages with
- ``is_error_frame`` set to True and ``arbitration_id`` will identify
- the error (default True)
+ :param receive_own_messages:
+ Enable self-reception of sent messages.
+
+ :param poll_interval:
+ Poll interval in seconds.
:raises ~can.exceptions.CanInitializationError:
If starting communication fails
@@ -73,52 +95,85 @@ def __init__(
if nixnet is None:
raise CanInterfaceNotImplementedError("The NI-XNET API has not been loaded")
- self._rx_queue = []
+ self.nixnet = nixnet
+
+ self._rx_queue = SimpleQueue() # type: ignore[var-annotated]
self.channel = channel
self.channel_info = "NI-XNET: " + channel
- # Set database for the initialization
- if not fd:
- database_name = ":memory:"
- else:
- if not brs:
- database_name = ":can_fd:"
- else:
- database_name = ":can_fd_brs:"
+ self.poll_interval = poll_interval
- try:
+ self.fd = isinstance(timing, BitTimingFd) if timing else fd
- # We need two sessions for this application, one to send frames and another to receive them
+ # Set database for the initialization
+ database_name = ":can_fd_brs:" if self.fd else ":memory:"
- self.__session_send = nixnet.session.FrameOutStreamSession(
+ try:
+ # We need two sessions for this application,
+ # one to send frames and another to receive them
+ self._session_send = nixnet.session.FrameOutStreamSession(
channel, database_name=database_name
)
- self.__session_receive = nixnet.session.FrameInStreamSession(
+ self._session_receive = nixnet.session.FrameInStreamSession(
channel, database_name=database_name
)
+ self._interface = self._session_send.intf
+
+ # set interface properties
+ self._interface.can_lstn_only = kwargs.get("listen_only", False)
+ self._interface.echo_tx = receive_own_messages
+ self._interface.bus_err_to_in_strm = True
+
+ if isinstance(timing, BitTimingFd):
+ timing = check_or_adjust_timing_clock(timing, [40_000_000])
+ custom_nom_baud_rate = ( # nxPropSession_IntfBaudRate64
+ 0xA0000000
+ + (timing.nom_tq << 32)
+ + (timing.nom_sjw - 1 << 16)
+ + (timing.nom_tseg1 - 1 << 8)
+ + (timing.nom_tseg2 - 1)
+ )
+ custom_data_baud_rate = ( # nxPropSession_IntfCanFdBaudRate64
+ 0xA0000000
+ + (timing.data_tq << 13)
+ + (timing.data_tseg1 - 1 << 8)
+ + (timing.data_tseg2 - 1 << 4)
+ + (timing.data_sjw - 1)
+ )
+ self._interface.baud_rate = custom_nom_baud_rate
+ self._interface.can_fd_baud_rate = custom_data_baud_rate
+ elif isinstance(timing, BitTiming):
+ timing = check_or_adjust_timing_clock(timing, [40_000_000])
+ custom_baud_rate = ( # nxPropSession_IntfBaudRate64
+ 0xA0000000
+ + (timing.tq << 32)
+ + (timing.sjw - 1 << 16)
+ + (timing.tseg1 - 1 << 8)
+ + (timing.tseg2 - 1)
+ )
+ self._interface.baud_rate = custom_baud_rate
+ else:
+ # See page 1017 of NI-XNET Hardware and Software Manual
+ # to set custom can configuration
+ if bitrate:
+ self._interface.baud_rate = bitrate
+
+ if self.fd:
+ # See page 951 of NI-XNET Hardware and Software Manual
+ # to set custom can configuration
+ self._interface.can_fd_baud_rate = fd_bitrate or bitrate
+
+ _can_termination = (
+ nixnet.constants.CanTerm.ON
+ if can_termination
+ else nixnet.constants.CanTerm.OFF
+ )
+ self._interface.can_term = _can_termination
- # We stop the sessions to allow reconfiguration, as by default they autostart at creation
- self.__session_send.stop()
- self.__session_receive.stop()
-
- # See page 1017 of NI-XNET Hardware and Software Manual to set custom can configuration
- if bitrate:
- self.__session_send.intf.baud_rate = bitrate
- self.__session_receive.intf.baud_rate = bitrate
-
- if fd_bitrate:
- # See page 951 of NI-XNET Hardware and Software Manual to set custom can configuration
- self.__session_send.intf.can_fd_baud_rate = fd_bitrate
- self.__session_receive.intf.can_fd_baud_rate = fd_bitrate
-
- if can_termination:
- self.__session_send.intf.can_term = nixnet.constants.CanTerm.ON
- self.__session_receive.intf.can_term = nixnet.constants.CanTerm.ON
-
- self.__session_receive.queue_size = 512
- # Once that all the parameters have been restarted, we start the sessions
- self.__session_send.start()
- self.__session_receive.start()
+ # self._session_receive.queue_size = 512
+ # Once that all the parameters have been set, we start the sessions
+ self._session_send.start()
+ self._session_receive.start()
except nixnet.errors.XnetError as error:
raise CanInitializationError(
@@ -130,44 +185,65 @@ def __init__(
channel=channel,
can_filters=can_filters,
bitrate=bitrate,
- log_errors=log_errors,
**kwargs,
)
- def _recv_internal(self, timeout):
- try:
- if len(self._rx_queue) == 0:
- fr = self.__session_receive.frames.read(4, timeout=0)
- for f in fr:
- self._rx_queue.append(f)
- can_frame = self._rx_queue.pop(0)
-
- # Timestamp should be converted from raw frame format(100ns increment from(12:00 a.m. January 1 1601 Coordinated
- # Universal Time (UTC)) to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT))
+ def _recv_internal(
+ self, timeout: Optional[float]
+ ) -> Tuple[Optional[Message], bool]:
+ end_time = time.perf_counter() + timeout if timeout is not None else None
+
+ while True:
+ # try to read all available frames
+ for frame in self._session_receive.frames.read(1024, timeout=0):
+ self._rx_queue.put_nowait(frame)
+
+ if self._rx_queue.qsize():
+ break
+
+ # check for timeout
+ if end_time is not None and time.perf_counter() > end_time:
+ return None, False
+
+ # Wait a short time until we try to read again
+ time.sleep(self.poll_interval)
+
+ can_frame = self._rx_queue.get_nowait()
+
+ # Timestamp should be converted from raw frame format(100ns increment
+ # from(12:00 a.m. January 1 1601 Coordinated Universal Time (UTC))
+ # to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT))
+ timestamp = can_frame.timestamp * 1e-7 - 11_644_473_600
+ if can_frame.type is self.nixnet.constants.FrameType.CAN_BUS_ERROR:
msg = Message(
- timestamp=can_frame.timestamp / 10000000.0 - 11644473600,
+ timestamp=timestamp,
channel=self.channel,
- is_remote_frame=can_frame.type == nixnet.constants.FrameType.CAN_REMOTE,
- is_error_frame=can_frame.type
- == nixnet.constants.FrameType.CAN_BUS_ERROR,
+ is_error_frame=True,
+ )
+ else:
+ msg = Message(
+ timestamp=timestamp,
+ channel=self.channel,
+ is_remote_frame=can_frame.type
+ is self.nixnet.constants.FrameType.CAN_REMOTE,
+ is_error_frame=False,
is_fd=(
- can_frame.type == nixnet.constants.FrameType.CANFD_DATA
- or can_frame.type == nixnet.constants.FrameType.CANFDBRS_DATA
+ can_frame.type is self.nixnet.constants.FrameType.CANFD_DATA
+ or can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA
+ ),
+ bitrate_switch=(
+ can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA
),
- bitrate_switch=can_frame.type
- == nixnet.constants.FrameType.CANFDBRS_DATA,
is_extended_id=can_frame.identifier.extended,
# Get identifier from CanIdentifier structure
arbitration_id=can_frame.identifier.identifier,
dlc=len(can_frame.payload),
data=can_frame.payload,
+ is_rx=not can_frame.echo,
)
+ return msg, False
- return msg, self._filters is None
- except Exception:
- return None, self._filters is None
-
- def send(self, msg, timeout=None):
+ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""
Send a message using NI-XNET.
@@ -182,80 +258,83 @@ def send(self, msg, timeout=None):
It does not wait for message to be ACKed currently.
"""
if timeout is None:
- timeout = nixnet.constants.TIMEOUT_INFINITE
+ timeout = self.nixnet.constants.TIMEOUT_INFINITE
if msg.is_remote_frame:
- type_message = nixnet.constants.FrameType.CAN_REMOTE
+ type_message = self.nixnet.constants.FrameType.CAN_REMOTE
elif msg.is_error_frame:
- type_message = nixnet.constants.FrameType.CAN_BUS_ERROR
+ type_message = self.nixnet.constants.FrameType.CAN_BUS_ERROR
elif msg.is_fd:
if msg.bitrate_switch:
- type_message = nixnet.constants.FrameType.CANFDBRS_DATA
+ type_message = self.nixnet.constants.FrameType.CANFDBRS_DATA
else:
- type_message = nixnet.constants.FrameType.CANFD_DATA
+ type_message = self.nixnet.constants.FrameType.CANFD_DATA
else:
- type_message = nixnet.constants.FrameType.CAN_DATA
+ type_message = self.nixnet.constants.FrameType.CAN_DATA
- can_frame = nixnet.types.CanFrame(
- nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
+ can_frame = self.nixnet.types.CanFrame(
+ self.nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
type=type_message,
payload=msg.data,
)
try:
- self.__session_send.frames.write([can_frame], timeout)
- except nixnet.errors.XnetError as error:
+ self._session_send.frames.write([can_frame], timeout)
+ except self.nixnet.errors.XnetError as error:
raise CanOperationError(
f"{error.args[0]} ({error.error_type})", error.error_code
) from None
- def reset(self):
+ def reset(self) -> None:
"""
Resets network interface. Stops network interface, then resets the CAN
chip to clear the CAN error counters (clear error passive state).
Resetting includes clearing all entries from read and write queues.
"""
- self.__session_send.flush()
- self.__session_receive.flush()
+ self._session_send.flush()
+ self._session_receive.flush()
- self.__session_send.stop()
- self.__session_receive.stop()
+ self._session_send.stop()
+ self._session_receive.stop()
- self.__session_send.start()
- self.__session_receive.start()
+ self._session_send.start()
+ self._session_receive.start()
- def shutdown(self):
+ def shutdown(self) -> None:
"""Close object."""
super().shutdown()
- self.__session_send.flush()
- self.__session_receive.flush()
-
- self.__session_send.stop()
- self.__session_receive.stop()
+ if hasattr(self, "_session_send"):
+ self._session_send.flush()
+ self._session_send.stop()
+ self._session_send.close()
- self.__session_send.close()
- self.__session_receive.close()
+ if hasattr(self, "_session_receive"):
+ self._session_receive.flush()
+ self._session_receive.stop()
+ self._session_receive.close()
@staticmethod
- def _detect_available_configs():
+ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
configs = []
try:
- with nixnet.system.System() as nixnet_system:
+ with nixnet.system.System() as nixnet_system: # type: ignore[union-attr]
for interface in nixnet_system.intf_refs_can:
- cahnnel = str(interface)
+ channel = str(interface)
logger.debug(
- "Found channel index %d: %s", interface.port_num, cahnnel
+ "Found channel index %d: %s", interface.port_num, channel
)
configs.append(
{
"interface": "nixnet",
- "channel": cahnnel,
+ "channel": channel,
"can_term_available": interface.can_term_cap
- == nixnet.constants.CanTermCap.YES,
+ is nixnet.constants.CanTermCap.YES, # type: ignore[union-attr]
+ "supports_fd": interface.can_tcvr_cap
+ is nixnet.constants.CanTcvrCap.HS, # type: ignore[union-attr]
}
)
except Exception as error:
logger.debug("An error occured while searching for configs: %s", str(error))
- return configs
+ return configs # type: ignore
diff --git a/setup.py b/setup.py
index bada45b77..8cabc8c02 100644
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,7 @@
"cantact": ["cantact>=0.0.7"],
"cvector": ["python-can-cvector"],
"gs_usb": ["gs_usb>=0.2.1"],
- "nixnet": ["nixnet>=0.3.1"],
+ "nixnet": ["nixnet>=0.3.2"],
"pcan": ["uptime~=3.0.1"],
"remote": ["python-can-remote"],
"sontheim": ["python-can-sontheim>=0.1.2"],
From b16f8aa6529cacb4d57736dac77c214328953fca Mon Sep 17 00:00:00 2001
From: mikisama <41532794+mikisama@users.noreply.github.com>
Date: Sat, 4 Feb 2023 21:10:45 +0800
Subject: [PATCH 239/475] Improve slcan.py (#1490)
* Improve slcan.py
1. Improve receiving performance
2. Fix an issue that the first read may blocking even if the `timeout`
parameter is not `None`.
* use `read_all` to read out serial port data.
* fix when the `timeout` parameter is 0, it cannot enter the while loop.
* For performance reason, revert to using `read` to read serial data.
* add `size=1` and renamed `new_data` to `new_byte`
to make the code easier to understand and read.
* fix the issue of returning `None` before receiving
the full SLCAN message.
* Due to the simplification of the control flow,
the class variable `self._buffer` can be replaced
by the local variable `buffer`.
* Revert "Due to the simplification of the control flow,"
This reverts commit 3eb80b9f40d021bc8671274e4ad89845b03582f1.
* Simplify the control flow for finding the end of a SLCAN message.
* improve the `timeout` handling of slcan.py
* Change the plain format calls to f-strings.
* using `bytearray.fromhex` to parse the frame's data.
* change `del self._buffer[:]` to `self._buffer.clear` since it's more readable.
* Simplify the timeout handling
* fix the issue when the returned string is `None`.
* using `pyserial.reset_input_buffer` to discard
the data in the input buffer.
* fix failing PyPy test
* fix failing PyPy test
* improve the comments
---
can/interfaces/slcan.py | 147 ++++++++++++++++------------------------
test/test_slcan.py | 33 ++++++---
2 files changed, 81 insertions(+), 99 deletions(-)
diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py
index 212c4c85c..bcb3121ca 100644
--- a/can/interfaces/slcan.py
+++ b/can/interfaces/slcan.py
@@ -64,6 +64,7 @@ def __init__(
btr: Optional[str] = None,
sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN,
rtscts: bool = False,
+ timeout: float = 0.001,
**kwargs: Any,
) -> None:
"""
@@ -82,7 +83,8 @@ def __init__(
Time to wait in seconds after opening serial connection
:param rtscts:
turn hardware handshake (RTS/CTS) on and off
-
+ :param timeout:
+ Timeout for the serial or usb device in seconds (default 0.001)
:raise ValueError: if both ``bitrate`` and ``btr`` are set or the channel is invalid
:raise CanInterfaceNotImplementedError: if the serial module is missing
:raise CanInitializationError: if the underlying serial connection could not be established
@@ -98,7 +100,10 @@ def __init__(
with error_check(exception_type=CanInitializationError):
self.serialPortOrig = serial.serial_for_url(
- channel, baudrate=ttyBaudrate, rtscts=rtscts
+ channel,
+ baudrate=ttyBaudrate,
+ rtscts=rtscts,
+ timeout=timeout,
)
self._buffer = bytearray()
@@ -150,46 +155,34 @@ def _write(self, string: str) -> None:
self.serialPortOrig.flush()
def _read(self, timeout: Optional[float]) -> Optional[str]:
+ _timeout = serial.Timeout(timeout)
with error_check("Could not read from serial device"):
- # first read what is already in receive buffer
- while self.serialPortOrig.in_waiting:
- self._buffer += self.serialPortOrig.read()
- # if we still don't have a complete message, do a blocking read
- start = time.time()
- time_left = timeout
- while not (
- ord(self._OK) in self._buffer or ord(self._ERROR) in self._buffer
- ):
- self.serialPortOrig.timeout = time_left
- byte = self.serialPortOrig.read()
- if byte:
- self._buffer += byte
- # if timeout is None, try indefinitely
- if timeout is None:
- continue
- # try next one only if there still is time, and with
- # reduced timeout
- else:
- time_left = timeout - (time.time() - start)
- if time_left > 0:
- continue
+ while True:
+ # Due to accessing `serialPortOrig.in_waiting` too often will reduce the performance.
+ # We read the `serialPortOrig.in_waiting` only once here.
+ in_waiting = self.serialPortOrig.in_waiting
+ for _ in range(max(1, in_waiting)):
+ new_byte = self.serialPortOrig.read(size=1)
+ if new_byte:
+ self._buffer.extend(new_byte)
else:
- return None
+ break
+
+ if new_byte in (self._ERROR, self._OK):
+ string = self._buffer.decode()
+ self._buffer.clear()
+ return string
+
+ if _timeout.expired():
+ break
- # return first message
- for i in range(len(self._buffer)):
- if self._buffer[i] == ord(self._OK) or self._buffer[i] == ord(self._ERROR):
- string = self._buffer[: i + 1].decode()
- del self._buffer[: i + 1]
- break
- return string
+ return None
def flush(self) -> None:
- del self._buffer[:]
+ self._buffer.clear()
with error_check("Could not flush"):
- while self.serialPortOrig.in_waiting:
- self.serialPortOrig.read()
+ self.serialPortOrig.reset_input_buffer()
def open(self) -> None:
self._write("O")
@@ -204,7 +197,7 @@ def _recv_internal(
canId = None
remote = False
extended = False
- frame = []
+ data = None
string = self._read(timeout)
@@ -215,14 +208,12 @@ def _recv_internal(
canId = int(string[1:9], 16)
dlc = int(string[9])
extended = True
- for i in range(0, dlc):
- frame.append(int(string[10 + i * 2 : 12 + i * 2], 16))
+ data = bytearray.fromhex(string[10 : 10 + dlc * 2])
elif string[0] == "t":
# normal frame
canId = int(string[1:4], 16)
dlc = int(string[4])
- for i in range(0, dlc):
- frame.append(int(string[5 + i * 2 : 7 + i * 2], 16))
+ data = bytearray.fromhex(string[5 : 5 + dlc * 2])
elif string[0] == "r":
# remote frame
canId = int(string[1:4], 16)
@@ -242,7 +233,7 @@ def _recv_internal(
timestamp=time.time(), # Better than nothing...
is_remote_frame=remote,
dlc=dlc,
- data=frame,
+ data=data,
)
return msg, False
return None, False
@@ -252,15 +243,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
self.serialPortOrig.write_timeout = timeout
if msg.is_remote_frame:
if msg.is_extended_id:
- sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc)
+ sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}"
else:
- sendStr = "r%03X%d" % (msg.arbitration_id, msg.dlc)
+ sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}"
else:
if msg.is_extended_id:
- sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc)
+ sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}"
else:
- sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc)
- sendStr += "".join(["%02X" % b for b in msg.data])
+ sendStr = f"t{msg.arbitration_id:03X}{msg.dlc:d}"
+ sendStr += msg.data.hex().upper()
self._write(sendStr)
def shutdown(self) -> None:
@@ -295,29 +286,17 @@ def get_version(
cmd = "V"
self._write(cmd)
- start = time.time()
- time_left = timeout
- while True:
- string = self._read(time_left)
-
- if not string:
- pass
- elif string[0] == cmd and len(string) == 6:
- # convert ASCII coded version
- hw_version = int(string[1:3])
- sw_version = int(string[3:5])
- return hw_version, sw_version
- # if timeout is None, try indefinitely
- if timeout is None:
- continue
- # try next one only if there still is time, and with
- # reduced timeout
- else:
- time_left = timeout - (time.time() - start)
- if time_left > 0:
- continue
- else:
- return None, None
+ string = self._read(timeout)
+
+ if not string:
+ pass
+ elif string[0] == cmd and len(string) == 6:
+ # convert ASCII coded version
+ hw_version = int(string[1:3])
+ sw_version = int(string[3:5])
+ return hw_version, sw_version
+
+ return None, None
def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
"""Get serial number of the slcan interface.
@@ -331,24 +310,12 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
cmd = "N"
self._write(cmd)
- start = time.time()
- time_left = timeout
- while True:
- string = self._read(time_left)
-
- if not string:
- pass
- elif string[0] == cmd and len(string) == 6:
- serial_number = string[1:-1]
- return serial_number
- # if timeout is None, try indefinitely
- if timeout is None:
- continue
- # try next one only if there still is time, and with
- # reduced timeout
- else:
- time_left = timeout - (time.time() - start)
- if time_left > 0:
- continue
- else:
- return None
+ string = self._read(timeout)
+
+ if not string:
+ pass
+ elif string[0] == cmd and len(string) == 6:
+ serial_number = string[1:-1]
+ return serial_number
+
+ return None
diff --git a/test/test_slcan.py b/test/test_slcan.py
index aa97e518b..8db2d402a 100644
--- a/test/test_slcan.py
+++ b/test/test_slcan.py
@@ -2,11 +2,26 @@
import unittest
import can
+from .config import IS_PYPY
+
+
+"""
+Mentioned in #1010 & #1490
+
+> PyPy works best with pure Python applications. Whenever you use a C extension module,
+> it runs much slower than in CPython. The reason is that PyPy can't optimize C extension modules since they're not fully supported.
+> In addition, PyPy has to emulate reference counting for that part of the code, making it even slower.
+
+https://realpython.com/pypy-faster-python/#it-doesnt-work-well-with-c-extensions
+"""
+TIMEOUT = 0.5 if IS_PYPY else 0.001 # 0.001 is the default set in slcanBus
class slcanTestCase(unittest.TestCase):
def setUp(self):
- self.bus = can.Bus("loop://", interface="slcan", sleep_after_open=0)
+ self.bus = can.Bus(
+ "loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT
+ )
self.serial = self.bus.serialPortOrig
self.serial.read(self.serial.in_waiting)
@@ -15,7 +30,7 @@ def tearDown(self):
def test_recv_extended(self):
self.serial.write(b"T12ABCDEF2AA55\r")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
self.assertEqual(msg.is_extended_id, True)
@@ -33,7 +48,7 @@ def test_send_extended(self):
def test_recv_standard(self):
self.serial.write(b"t4563112233\r")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
self.assertEqual(msg.arbitration_id, 0x456)
self.assertEqual(msg.is_extended_id, False)
@@ -51,7 +66,7 @@ def test_send_standard(self):
def test_recv_standard_remote(self):
self.serial.write(b"r1238\r")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
self.assertEqual(msg.arbitration_id, 0x123)
self.assertEqual(msg.is_extended_id, False)
@@ -68,7 +83,7 @@ def test_send_standard_remote(self):
def test_recv_extended_remote(self):
self.serial.write(b"R12ABCDEF6\r")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
self.assertEqual(msg.is_extended_id, True)
@@ -85,11 +100,11 @@ def test_send_extended_remote(self):
def test_partial_recv(self):
self.serial.write(b"T12ABCDEF")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNone(msg)
self.serial.write(b"2AA55\rT12")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
self.assertEqual(msg.is_extended_id, True)
@@ -97,11 +112,11 @@ def test_partial_recv(self):
self.assertEqual(msg.dlc, 2)
self.assertSequenceEqual(msg.data, [0xAA, 0x55])
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNone(msg)
self.serial.write(b"ABCDEF2AA55\r")
- msg = self.bus.recv(0)
+ msg = self.bus.recv(TIMEOUT)
self.assertIsNotNone(msg)
def test_version(self):
From 75fdfe45c43746532173152a05ac50c647d912bb Mon Sep 17 00:00:00 2001
From: Lukas Magel
Date: Mon, 6 Feb 2023 09:23:15 +0100
Subject: [PATCH 240/475] Implement BitTiming/BitTimingFD handling for PCAN Bus
(#1514)
* add BitTiming parameter to PcanBus
* Move valid CAN/FD clocks to pcan/basic
* Add additional PCAN constructor tests
* Add tests for BitTiming with PCAN constructor
* Unify PCAN constructor code paths for FD with/without timing
---------
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/interfaces/pcan/basic.py | 10 +++++
can/interfaces/pcan/pcan.py | 78 +++++++++++++++++++++++------------
test/test_pcan.py | 79 +++++++++++++++++++++++++++++++++---
3 files changed, 135 insertions(+), 32 deletions(-)
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index 5f161eecc..b2624802a 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -655,6 +655,16 @@ class TPCANChannelInformation(Structure):
"PCAN_LANBUS16": PCAN_LANBUS16,
}
+VALID_PCAN_CAN_CLOCKS = [8_000_000]
+
+VALID_PCAN_FD_CLOCKS = [
+ 20_000_000,
+ 24_000_000,
+ 30_000_000,
+ 40_000_000,
+ 60_000_000,
+ 80_000_000,
+]
# ///////////////////////////////////////////////////////////
# PCAN-Basic API function declarations
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 01adbe0c2..2ed0ff445 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -5,20 +5,21 @@
import time
from datetime import datetime
import platform
-from typing import Optional, List, Tuple
+from typing import Optional, List, Tuple, Union, Any
from packaging import version
from can import (
BusABC,
BusState,
+ BitTiming,
+ BitTimingFd,
+ Message,
CanError,
CanOperationError,
CanInitializationError,
- Message,
)
-from can.util import len2dlc, dlc2len
-
+from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc
from .basic import (
PCAN_BITRATES,
PCAN_FD_PARAMETER_LIST,
@@ -61,8 +62,11 @@
FEATURE_FD_CAPABLE,
PCAN_DICT_STATUS,
PCAN_BUSOFF_AUTORESET,
+ TPCANBaudrate,
PCAN_ATTACHED_CHANNELS,
TPCANChannelInformation,
+ VALID_PCAN_FD_CLOCKS,
+ VALID_PCAN_CAN_CLOCKS,
)
@@ -112,12 +116,12 @@
class PcanBus(BusABC):
def __init__(
self,
- channel="PCAN_USBBUS1",
- device_id=None,
- state=BusState.ACTIVE,
- bitrate=500000,
- *args,
- **kwargs,
+ channel: str = "PCAN_USBBUS1",
+ device_id: Optional[int] = None,
+ state: BusState = BusState.ACTIVE,
+ timing: Optional[Union[BitTiming, BitTimingFd]] = None,
+ bitrate: int = 500000,
+ **kwargs: Any,
):
"""A PCAN USB interface to CAN.
@@ -142,6 +146,18 @@ def __init__(
BusState of the channel.
Default is ACTIVE
+ :param timing:
+ An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd`
+ to specify the bit timing parameters for the PCAN interface. If this parameter
+ is provided, it takes precedence over all other timing-related parameters.
+ If this parameter is not provided, the bit timing parameters can be specified
+ using the `bitrate` parameter for standard CAN or the `fd`, `f_clock`,
+ `f_clock_mhz`, `nom_brp`, `nom_tseg1`, `nom_tseg2`, `nom_sjw`, `data_brp`,
+ `data_tseg1`, `data_tseg2`, and `data_sjw` parameters for CAN FD.
+ Note that the `f_clock` value of the `timing` instance must be 8_000_000
+ for standard CAN or any of the following values for CAN FD: 20_000_000,
+ 24_000_000, 30_000_000, 40_000_000, 60_000_000, 80_000_000.
+
:param int bitrate:
Bitrate of channel in bit/s.
Default is 500 kbit/s.
@@ -231,8 +247,7 @@ def __init__(
raise ValueError(err_msg)
self.channel_info = str(channel)
- self.fd = kwargs.get("fd", False)
- pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K)
+ self.fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False)
hwtype = PCAN_TYPE_ISA
ioport = 0x02A0
@@ -245,30 +260,41 @@ def __init__(
self.check_api_version()
- if state is BusState.ACTIVE or state is BusState.PASSIVE:
+ if state in [BusState.ACTIVE, BusState.PASSIVE]:
self.state = state
else:
raise ValueError("BusState must be Active or Passive")
- if self.fd:
- f_clock_val = kwargs.get("f_clock", None)
- if f_clock_val is None:
- f_clock = "{}={}".format("f_clock_mhz", kwargs.get("f_clock_mhz", None))
- else:
- f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None))
-
- fd_parameters_values = [f_clock] + [
- f"{key}={kwargs.get(key, None)}"
- for key in PCAN_FD_PARAMETER_LIST
- if kwargs.get(key, None) is not None
+ if isinstance(timing, BitTiming):
+ timing = check_or_adjust_timing_clock(timing, VALID_PCAN_CAN_CLOCKS)
+ pcan_bitrate = TPCANBaudrate(timing.btr0 << 8 | timing.btr1)
+ result = self.m_objPCANBasic.Initialize(
+ self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt
+ )
+ elif self.fd:
+ if isinstance(timing, BitTimingFd):
+ timing = check_or_adjust_timing_clock(
+ timing, sorted(VALID_PCAN_FD_CLOCKS, reverse=True)
+ )
+ # We dump the timing parameters into the kwargs because they have equal names
+ # as the kwargs parameters and this saves us one additional code path
+ kwargs.update(timing)
+
+ clock_param = "f_clock" if "f_clock" in kwargs else "f_clock_mhz"
+ fd_parameters_values = [
+ f"{key}={kwargs[key]}"
+ for key in (clock_param,) + PCAN_FD_PARAMETER_LIST
+ if key in kwargs
]
- self.fd_bitrate = " ,".join(fd_parameters_values).encode("ascii")
+ self.fd_bitrate = ", ".join(fd_parameters_values).encode("ascii")
result = self.m_objPCANBasic.InitializeFD(
self.m_PcanHandle, self.fd_bitrate
)
+
else:
+ pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K)
result = self.m_objPCANBasic.Initialize(
self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt
)
@@ -312,7 +338,7 @@ def __init__(
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
- super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs)
+ super().__init__(channel=channel, state=state, bitrate=bitrate, **kwargs)
def _find_channel_by_dev_id(self, device_id):
"""
diff --git a/test/test_pcan.py b/test/test_pcan.py
index 01dac848c..aa0988a48 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -3,20 +3,18 @@
"""
import ctypes
-import platform
import unittest
from unittest import mock
from unittest.mock import Mock, patch
-
import pytest
from parameterized import parameterized
import can
from can.bus import BusState
from can.exceptions import CanInitializationError
-from can.interfaces.pcan.basic import *
from can.interfaces.pcan import PcanBus, PcanError
+from can.interfaces.pcan.basic import *
class TestPCANBus(unittest.TestCase):
@@ -53,8 +51,10 @@ def _mockGetValue(self, channel, parameter):
def test_bus_creation(self) -> None:
self.bus = can.Bus(interface="pcan")
+
self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
+
self.mock_pcan.Initialize.assert_called_once()
self.mock_pcan.InitializeFD.assert_not_called()
@@ -62,13 +62,41 @@ def test_bus_creation_state_error(self) -> None:
with self.assertRaises(ValueError):
can.Bus(interface="pcan", state=BusState.ERROR)
- def test_bus_creation_fd(self) -> None:
- self.bus = can.Bus(interface="pcan", fd=True)
+ @parameterized.expand([("f_clock", 8_000_000), ("f_clock_mhz", 8)])
+ def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None:
+ self.bus = can.Bus(
+ interface="pcan",
+ fd=True,
+ nom_brp=1,
+ nom_tseg1=129,
+ nom_tseg2=30,
+ nom_sjw=1,
+ data_brp=1,
+ data_tseg1=9,
+ data_tseg2=6,
+ data_sjw=1,
+ channel="PCAN_USBBUS1",
+ **{clock_param: clock_val},
+ )
+
self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
self.mock_pcan.Initialize.assert_not_called()
self.mock_pcan.InitializeFD.assert_called_once()
+ # Retrieve second argument of first call
+ bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1]
+
+ self.assertTrue(f"{clock_param}={clock_val}".encode("ascii") in bitrate_arg)
+ self.assertTrue(b"nom_brp=1" in bitrate_arg)
+ self.assertTrue(b"nom_tseg1=129" in bitrate_arg)
+ self.assertTrue(b"nom_tseg2=30" in bitrate_arg)
+ self.assertTrue(b"nom_sjw=1" in bitrate_arg)
+ self.assertTrue(b"data_brp=1" in bitrate_arg)
+ self.assertTrue(b"data_tseg1=9" in bitrate_arg)
+ self.assertTrue(b"data_tseg2=6" in bitrate_arg)
+ self.assertTrue(b"data_sjw=1" in bitrate_arg)
+
def test_api_version_low(self) -> None:
self.PCAN_API_VERSION_SIM = "1.0"
with self.assertLogs("can.pcan", level="WARNING") as cm:
@@ -333,6 +361,11 @@ def test_state(self, name, bus_state: BusState, expected_parameter) -> None:
(PCAN_USBBUS1, PCAN_LISTEN_ONLY, expected_parameter),
)
+ def test_state_constructor(self):
+ for state in [BusState.ACTIVE, BusState.PASSIVE]:
+ bus = can.Bus(interface="pcan", state=state)
+ assert bus.state == state
+
def test_detect_available_configs(self) -> None:
if platform.system() == "Darwin":
self.mock_pcan.GetValue = Mock(
@@ -381,7 +414,8 @@ def get_value_side_effect(handle, param):
self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect)
if expected_result == "error":
- self.assertRaises(ValueError, can.Bus, interface="pcan", device_id=dev_id)
+ with self.assertRaises(ValueError):
+ can.Bus(interface="pcan", device_id=dev_id)
else:
self.bus = can.Bus(interface="pcan", device_id=dev_id)
self.assertEqual(expected_result, self.bus.channel_info)
@@ -416,6 +450,39 @@ def test_peak_fd_bus_constructor_regression(self):
can.Bus(**params)
+ def test_constructor_bit_timing(self):
+ timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x47, btr1=0x2F)
+ can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)
+
+ bitrate_arg = self.mock_pcan.Initialize.call_args[0][1]
+ self.assertEqual(bitrate_arg.value, 0x472F)
+
+ def test_constructor_bit_timing_fd(self):
+ timing = can.BitTimingFd(
+ f_clock=40_000_000,
+ nom_brp=1,
+ nom_tseg1=129,
+ nom_tseg2=30,
+ nom_sjw=1,
+ data_brp=1,
+ data_tseg1=9,
+ data_tseg2=6,
+ data_sjw=1,
+ )
+ can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)
+
+ bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1]
+
+ self.assertTrue(b"f_clock=40000000" in bitrate_arg)
+ self.assertTrue(b"nom_brp=1" in bitrate_arg)
+ self.assertTrue(b"nom_tseg1=129" in bitrate_arg)
+ self.assertTrue(b"nom_tseg2=30" in bitrate_arg)
+ self.assertTrue(b"nom_sjw=1" in bitrate_arg)
+ self.assertTrue(b"data_brp=1" in bitrate_arg)
+ self.assertTrue(b"data_tseg1=9" in bitrate_arg)
+ self.assertTrue(b"data_tseg2=6" in bitrate_arg)
+ self.assertTrue(b"data_sjw=1" in bitrate_arg)
+
if __name__ == "__main__":
unittest.main()
From 40968170de9ff909f401fb38a9978de162379692 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Tue, 7 Mar 2023 08:41:51 +0100
Subject: [PATCH 241/475] Update linters (#1534)
* update black to 23.1.0
* update mypy to 1.0.1
* update pylint to 2.16.4
* remove useless suppression
* fix warning
---
.pylintrc | 4 ++--
can/bus.py | 2 --
can/interface.py | 1 -
can/interfaces/__init__.py | 3 ++-
can/interfaces/ics_neovi/neovi_bus.py | 1 -
can/interfaces/iscan.py | 1 -
can/interfaces/pcan/basic.py | 15 +--------------
can/interfaces/serial/serial_can.py | 1 -
can/interfaces/slcan.py | 1 -
can/interfaces/udp_multicast/bus.py | 1 -
can/interfaces/usb2can/usb2canInterface.py | 2 --
can/interfaces/virtual.py | 1 -
can/io/asc.py | 2 --
can/io/canutils.py | 1 -
can/io/csv.py | 1 -
can/io/logger.py | 2 +-
can/io/player.py | 1 -
can/logconvert.py | 1 -
can/player.py | 1 -
can/thread_safe_bus.py | 2 --
examples/print_notifier.py | 1 -
examples/receive_all.py | 1 -
examples/send_one.py | 1 -
examples/serial_com.py | 1 -
examples/simple_log_converter.py | 3 +--
examples/vcan_filtered.py | 1 -
requirements-lint.txt | 6 +++---
setup.py | 2 --
test/back2back_test.py | 3 ---
test/serial_test.py | 1 -
test/simplecyclic_test.py | 1 -
test/test_pcan.py | 3 +--
test/test_player.py | 1 -
test/test_util.py | 1 -
34 files changed, 11 insertions(+), 59 deletions(-)
diff --git a/.pylintrc b/.pylintrc
index cc4c50d88..bdbf47613 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -498,5 +498,5 @@ min-public-methods=2
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
-overgeneral-exceptions=BaseException,
- Exception
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception
diff --git a/can/bus.py b/can/bus.py
index f29b8ea6e..292c754fb 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -93,7 +93,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
time_left = timeout
while True:
-
# try to get a message
msg, already_filtered = self._recv_internal(timeout=time_left)
@@ -109,7 +108,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
# try next one only if there still is time, and with
# reduced timeout
else:
-
time_left = timeout - (time() - start)
if time_left > 0:
diff --git a/can/interface.py b/can/interface.py
index 04fc84ae9..47b44fed1 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -171,7 +171,6 @@ def detect_available_configs(
result = []
for interface in interfaces:
-
try:
bus_class = _get_class_for_interface(interface)
except CanInterfaceNotImplementedError:
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 3065e9bfd..c9ca6ca55 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -35,7 +35,8 @@
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
- # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note".
+ # See https://docs.python.org/3/library/importlib.metadata.html#entry-points,
+ # "Compatibility Note".
if sys.version_info >= (3, 10):
BACKENDS.update(
{
diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py
index 4972ed479..c3abc9f67 100644
--- a/can/interfaces/ics_neovi/neovi_bus.py
+++ b/can/interfaces/ics_neovi/neovi_bus.py
@@ -40,7 +40,6 @@
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",
diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py
index e76d9d060..a27494b61 100644
--- a/can/interfaces/iscan.py
+++ b/can/interfaces/iscan.py
@@ -157,7 +157,6 @@ def shutdown(self) -> None:
class IscanError(CanError):
-
ERROR_CODES = {
0: "Success",
1: "No access to device",
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index b2624802a..d1b2121cb 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -670,6 +670,7 @@ class TPCANChannelInformation(Structure):
# PCAN-Basic API function declarations
# ///////////////////////////////////////////////////////////
+
# PCAN-Basic API class implementation
#
class PCANBasic:
@@ -719,7 +720,6 @@ def Initialize(
IOPort=c_uint(0),
Interrupt=c_ushort(0),
):
-
"""Initializes a PCAN Channel
Parameters:
@@ -744,7 +744,6 @@ def Initialize(
# Initializes a FD capable PCAN Channel
#
def InitializeFD(self, Channel, BitrateFD):
-
"""Initializes a FD capable PCAN Channel
Parameters:
@@ -775,7 +774,6 @@ def InitializeFD(self, Channel, BitrateFD):
# Uninitializes one or all PCAN Channels initialized by CAN_Initialize
#
def Uninitialize(self, Channel):
-
"""Uninitializes one or all PCAN Channels initialized by CAN_Initialize
Remarks:
@@ -797,7 +795,6 @@ def Uninitialize(self, Channel):
# Resets the receive and transmit queues of the PCAN Channel
#
def Reset(self, Channel):
-
"""Resets the receive and transmit queues of the PCAN Channel
Remarks:
@@ -819,7 +816,6 @@ def Reset(self, Channel):
# Gets the current status of a PCAN Channel
#
def GetStatus(self, Channel):
-
"""Gets the current status of a PCAN Channel
Parameters:
@@ -838,7 +834,6 @@ def GetStatus(self, Channel):
# Reads a CAN message from the receive queue of a PCAN Channel
#
def Read(self, Channel):
-
"""Reads a CAN message from the receive queue of a PCAN Channel
Remarks:
@@ -867,7 +862,6 @@ def Read(self, Channel):
# Reads a CAN message from the receive queue of a FD capable PCAN Channel
#
def ReadFD(self, Channel):
-
"""Reads a CAN message from the receive queue of a FD capable PCAN Channel
Remarks:
@@ -896,7 +890,6 @@ def ReadFD(self, Channel):
# Transmits a CAN message
#
def Write(self, Channel, MessageBuffer):
-
"""Transmits a CAN message
Parameters:
@@ -916,7 +909,6 @@ def Write(self, Channel, MessageBuffer):
# Transmits a CAN message over a FD capable PCAN Channel
#
def WriteFD(self, Channel, MessageBuffer):
-
"""Transmits a CAN message over a FD capable PCAN Channel
Parameters:
@@ -936,7 +928,6 @@ def WriteFD(self, Channel, MessageBuffer):
# Configures the reception filter
#
def FilterMessages(self, Channel, FromID, ToID, Mode):
-
"""Configures the reception filter
Remarks:
@@ -963,7 +954,6 @@ def FilterMessages(self, Channel, FromID, ToID, Mode):
# Retrieves a PCAN Channel value
#
def GetValue(self, Channel, Parameter):
-
"""Retrieves a PCAN Channel value
Remarks:
@@ -1026,7 +1016,6 @@ def GetValue(self, Channel, Parameter):
# error code, in any desired language
#
def SetValue(self, Channel, Parameter, Buffer):
-
"""Returns a descriptive text of a given TPCANStatus error
code, in any desired language
@@ -1069,7 +1058,6 @@ def SetValue(self, Channel, Parameter, Buffer):
raise
def GetErrorText(self, Error, Language=0):
-
"""Configures or sets a PCAN Channel value
Remarks:
@@ -1098,7 +1086,6 @@ def GetErrorText(self, Error, Language=0):
raise
def LookUpChannel(self, Parameters):
-
"""Finds a PCAN-Basic channel that matches with the given parameters
Remarks:
diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py
index c1507b4fa..d0df88fcd 100644
--- a/can/interfaces/serial/serial_can.py
+++ b/can/interfaces/serial/serial_can.py
@@ -175,7 +175,6 @@ def _recv_internal(
try:
rx_byte = self._ser.read()
if rx_byte and ord(rx_byte) == 0xAA:
-
s = self._ser.read(4)
timestamp = struct.unpack(" None:
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
-
canId = None
remote = False
extended = False
diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py
index 2ba1205b1..5c7bee3e8 100644
--- a/can/interfaces/udp_multicast/bus.py
+++ b/can/interfaces/udp_multicast/bus.py
@@ -239,7 +239,6 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket:
# configure the socket
try:
-
# set hop limit / TTL
ttl_as_binary = struct.pack("@I", self.hop_limit)
if self.ip_version == 4:
diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py
index 504b61c7b..bca40f8d3 100644
--- a/can/interfaces/usb2can/usb2canInterface.py
+++ b/can/interfaces/usb2can/usb2canInterface.py
@@ -99,7 +99,6 @@ def __init__(
serial: Optional[str] = None,
**kwargs,
):
-
self.can = Usb2CanAbstractionLayer(dll)
# get the serial number of the device
@@ -134,7 +133,6 @@ def send(self, msg, timeout=None):
raise CanOperationError("could not send message", error_code=status)
def _recv_internal(self, timeout):
-
messagerx = CanalMsg()
if timeout == 0:
diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py
index 25b7abfb0..ad8774147 100644
--- a/can/interfaces/virtual.py
+++ b/can/interfaces/virtual.py
@@ -74,7 +74,6 @@ def __init__(
self._open = True
with channels_lock:
-
# Create a new channel if one does not exist
if self.channel_id not in channels:
channels[self.channel_id] = []
diff --git a/can/io/asc.py b/can/io/asc.py
index eb59c0471..a380f6b16 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -174,7 +174,6 @@ def _process_data_string(
def _process_classic_can_frame(
self, line: str, msg_kwargs: Dict[str, Any]
) -> Message:
-
# CAN error frame
if line.strip()[0:10].lower() == "errorframe":
# Error Frame
@@ -423,7 +422,6 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None:
self.file.write(line)
def on_message_received(self, msg: Message) -> None:
-
if msg.is_error_frame:
self.log_event(f"{self.channel} ErrorFrame", msg.timestamp)
return
diff --git a/can/io/canutils.py b/can/io/canutils.py
index c57a6ca97..17d7a193f 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -48,7 +48,6 @@ def __init__(
def __iter__(self) -> Generator[Message, None, None]:
for line in self.file:
-
# skip empty lines
temp = line.strip()
if not temp:
diff --git a/can/io/csv.py b/can/io/csv.py
index ecfc5de35..7570d4f30 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -49,7 +49,6 @@ def __iter__(self) -> Generator[Message, None, None]:
return
for line in self.file:
-
timestamp, arbitration_id, extended, remote, error, dlc, data = line.split(
","
)
diff --git a/can/io/logger.py b/can/io/logger.py
index b6ea23380..0477fa065 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -237,7 +237,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter:
elif isinstance(logger, Printer) and logger.file is not None:
return cast(FileIOMessageWriter, logger)
- raise Exception(
+ raise ValueError(
f'The log format "{suffix}" '
f"is not supported by {self.__class__.__name__}. "
f"{self.__class__.__name__} supports the following formats: "
diff --git a/can/io/player.py b/can/io/player.py
index 0e062ecb7..13a9ce60e 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -139,7 +139,6 @@ def __iter__(self) -> typing.Generator[Message, None, None]:
t_skipped = 0.0
for message in self.raw_messages:
-
# Work out the correct wait time
if self.timestamps:
if recorded_start_time is None:
diff --git a/can/logconvert.py b/can/logconvert.py
index d89155758..7a34deb61 100644
--- a/can/logconvert.py
+++ b/can/logconvert.py
@@ -46,7 +46,6 @@ def main():
args = parser.parse_args()
with LogReader(args.input) as reader:
-
if args.file_size:
logger = SizedRotatingLogger(
base_filename=args.output, max_bytes=args.file_size
diff --git a/can/player.py b/can/player.py
index c029981be..fab271824 100644
--- a/can/player.py
+++ b/can/player.py
@@ -87,7 +87,6 @@ def main() -> None:
with _create_bus(results, **additional_config) as bus:
with LogReader(results.infile, **additional_config) as reader:
-
in_sync = MessageSync(
cast(Iterable[Message], reader),
timestamps=results.timestamps,
diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py
index 6cf28fd99..6f16b8b4d 100644
--- a/can/thread_safe_bus.py
+++ b/can/thread_safe_bus.py
@@ -58,9 +58,7 @@ def __init__(self, *args, **kwargs):
# now, BusABC.send_periodic() does not need a lock anymore, but the
# implementation still requires a context manager
- # pylint: disable=protected-access
self.__wrapped__._lock_send_periodic = nullcontext()
- # pylint: enable=protected-access
# init locks for sending and receiving separately
self._lock_send = RLock()
diff --git a/examples/print_notifier.py b/examples/print_notifier.py
index b6554ccd2..cb4a02799 100755
--- a/examples/print_notifier.py
+++ b/examples/print_notifier.py
@@ -5,7 +5,6 @@
def main():
-
with can.Bus(receive_own_messages=True) as bus:
print_listener = can.Printer()
can.Notifier(bus, [print_listener])
diff --git a/examples/receive_all.py b/examples/receive_all.py
index d8d8714fc..e9410e49f 100755
--- a/examples/receive_all.py
+++ b/examples/receive_all.py
@@ -14,7 +14,6 @@ def receive_all():
# this uses the default configuration (for example from environment variables, or a
# config file) see https://python-can.readthedocs.io/en/stable/configuration.html
with can.Bus() as bus:
-
# set to read-only, only supported on some interfaces
try:
bus.state = BusState.PASSIVE
diff --git a/examples/send_one.py b/examples/send_one.py
index 7e3fb8a4c..41b3a3cd0 100755
--- a/examples/send_one.py
+++ b/examples/send_one.py
@@ -13,7 +13,6 @@ def send_one():
# this uses the default configuration (for example from the config file)
# see https://python-can.readthedocs.io/en/stable/configuration.html
with can.Bus() as bus:
-
# Using specific buses works similar:
# bus = can.Bus(interface='socketcan', channel='vcan0', bitrate=250000)
# bus = can.Bus(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000)
diff --git a/examples/serial_com.py b/examples/serial_com.py
index 76b95c3e7..538c8d12f 100755
--- a/examples/serial_com.py
+++ b/examples/serial_com.py
@@ -50,7 +50,6 @@ def main():
"""Controls the sender and receiver."""
with can.Bus(interface="serial", channel="/dev/ttyS10") as server:
with can.Bus(interface="serial", channel="/dev/ttyS11") as client:
-
tx_msg = can.Message(
arbitration_id=0x01,
data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py
index e82669f54..20e8fba75 100755
--- a/examples/simple_log_converter.py
+++ b/examples/simple_log_converter.py
@@ -17,8 +17,7 @@ def main():
with can.LogReader(sys.argv[1]) as reader:
with can.Logger(sys.argv[2]) as writer:
-
- for msg in reader: # pylint: disable=not-an-iterable
+ for msg in reader:
writer.on_message_received(msg)
diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py
index d48a9d8cb..f022759fa 100755
--- a/examples/vcan_filtered.py
+++ b/examples/vcan_filtered.py
@@ -12,7 +12,6 @@
def main():
"""Send some messages to itself and apply filtering."""
with can.Bus(interface="virtual", receive_own_messages=True) as bus:
-
can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}]
bus.set_filters(can_filters)
diff --git a/requirements-lint.txt b/requirements-lint.txt
index 28bcf2aa2..f1070e1b9 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,5 +1,5 @@
-pylint==2.15.9
-black~=22.10.0
-mypy==0.991
+pylint==2.16.4
+black~=23.1.0
+mypy==1.0.1
mypy-extensions==0.4.3
types-setuptools
diff --git a/setup.py b/setup.py
index 8cabc8c02..96cbc0c77 100644
--- a/setup.py
+++ b/setup.py
@@ -5,8 +5,6 @@
Learn more at https://github.com/hardbyte/python-can/
"""
-# pylint: disable=invalid-name
-
from os import listdir
from os.path import isfile, join
import re
diff --git a/test/back2back_test.py b/test/back2back_test.py
index 54d619878..ce09b6179 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -275,7 +275,6 @@ def test_sub_second_timestamp_resolution(self):
@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan")
class BasicTestSocketCan(Back2BackTestCase):
-
INTERFACE_1 = "socketcan"
CHANNEL_1 = "vcan0"
INTERFACE_2 = "socketcan"
@@ -289,7 +288,6 @@ class BasicTestSocketCan(Back2BackTestCase):
"only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv4(Back2BackTestCase):
-
INTERFACE_1 = "udp_multicast"
CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv4
INTERFACE_2 = "udp_multicast"
@@ -329,7 +327,6 @@ def test_unique_message_instances(self):
@unittest.skipUnless(TEST_INTERFACE_ETAS, "skip testing of etas interface")
class BasicTestEtas(Back2BackTestCase):
-
if TEST_INTERFACE_ETAS:
configs = can.interface.detect_available_configs(interfaces="etas")
diff --git a/test/serial_test.py b/test/serial_test.py
index aa6c71994..e1df96435 100644
--- a/test/serial_test.py
+++ b/test/serial_test.py
@@ -44,7 +44,6 @@ def reset(self):
class SimpleSerialTestBase(ComparingMessagesTestCase):
-
MAX_TIMESTAMP = 0xFFFFFFFF / 1000
def __init__(self):
diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py
index 639694bfa..4454cbd27 100644
--- a/test/simplecyclic_test.py
+++ b/test/simplecyclic_test.py
@@ -33,7 +33,6 @@ def test_cycle_time(self):
with can.interface.Bus(interface="virtual") as bus1:
with can.interface.Bus(interface="virtual") as bus2:
-
# disabling the garbage collector makes the time readings more reliable
gc.disable()
diff --git a/test/test_pcan.py b/test/test_pcan.py
index aa0988a48..93a5f5ff4 100644
--- a/test/test_pcan.py
+++ b/test/test_pcan.py
@@ -19,7 +19,6 @@
class TestPCANBus(unittest.TestCase):
def setUp(self) -> None:
-
patcher = mock.patch("can.interfaces.pcan.pcan.PCANBasic", spec=True)
self.MockPCANBasic = patcher.start()
self.addCleanup(patcher.stop)
@@ -62,7 +61,7 @@ def test_bus_creation_state_error(self) -> None:
with self.assertRaises(ValueError):
can.Bus(interface="pcan", state=BusState.ERROR)
- @parameterized.expand([("f_clock", 8_000_000), ("f_clock_mhz", 8)])
+ @parameterized.expand([("f_clock", 80_000_000), ("f_clock_mhz", 80)])
def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None:
self.bus = can.Bus(
interface="pcan",
diff --git a/test/test_player.py b/test/test_player.py
index c15bf82e2..9bdd484b8 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -15,7 +15,6 @@
class TestPlayerScriptModule(unittest.TestCase):
-
logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc")
def setUp(self) -> None:
diff --git a/test/test_util.py b/test/test_util.py
index b6c261602..e77401688 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -21,7 +21,6 @@ class RenameKwargsTest(unittest.TestCase):
expected_kwargs = dict(a=1, b=2, c=3, d=4)
def _test(self, start: str, end: str, kwargs, aliases):
-
# Test that we do get the DeprecationWarning when called with deprecated kwargs
with self.assertWarnsRegex(
DeprecationWarning, "is deprecated.*?" + start + ".*?" + end
From fa5b133a40b7ee87386234249e18c82f1c789672 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Thu, 16 Mar 2023 11:50:48 +0100
Subject: [PATCH 242/475] improve ThreadBasedCyclicSendTask timing (#1539)
Co-authored-by: zariiii9003
---
can/broadcastmanager.py | 53 +++++++++++++++++++++++++++--------------
1 file changed, 35 insertions(+), 18 deletions(-)
diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py
index cfa4c658d..d795eb1fa 100644
--- a/can/broadcastmanager.py
+++ b/can/broadcastmanager.py
@@ -5,30 +5,39 @@
:meth:`can.BusABC.send_periodic`.
"""
+import abc
+import logging
+import sys
+import threading
+import time
from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING
+from typing_extensions import Final
+
from can import typechecking
+from can.message import Message
if TYPE_CHECKING:
from can.bus import BusABC
-from can.message import Message
-
-import abc
-import logging
-import threading
-import time
# try to import win32event for event-based cyclic send task (needs the pywin32 package)
+USE_WINDOWS_EVENTS = False
try:
import win32event
- HAS_EVENTS = True
+ # Python 3.11 provides a more precise sleep implementation on Windows, so this is not necessary.
+ # Put version check here, so mypy does not complain about `win32event` not being defined.
+ if sys.version_info < (3, 11):
+ USE_WINDOWS_EVENTS = True
except ImportError:
- HAS_EVENTS = False
+ pass
log = logging.getLogger("can.bcm")
+NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000
+NANOSECONDS_IN_MILLISECOND: Final[int] = 1_000_000
+
class CyclicTask(abc.ABC):
"""
@@ -64,6 +73,7 @@ def __init__(
# Take the Arbitration ID of the first element
self.arbitration_id = messages[0].arbitration_id
self.period = period
+ self.period_ns = int(round(period * 1e9))
self.messages = messages
@staticmethod
@@ -246,7 +256,7 @@ def __init__(
)
self.on_error = on_error
- if HAS_EVENTS:
+ if USE_WINDOWS_EVENTS:
self.period_ms = int(round(period * 1000, 0))
try:
self.event = win32event.CreateWaitableTimerEx(
@@ -261,7 +271,7 @@ def __init__(
self.start()
def stop(self) -> None:
- if HAS_EVENTS:
+ if USE_WINDOWS_EVENTS:
win32event.CancelWaitableTimer(self.event.handle)
self.stopped = True
@@ -272,7 +282,7 @@ def start(self) -> None:
self.thread = threading.Thread(target=self._run, name=name)
self.thread.daemon = True
- if HAS_EVENTS:
+ if USE_WINDOWS_EVENTS:
win32event.SetWaitableTimer(
self.event.handle, 0, self.period_ms, None, None, False
)
@@ -281,10 +291,11 @@ def start(self) -> None:
def _run(self) -> None:
msg_index = 0
+ msg_due_time_ns = time.perf_counter_ns()
+
while not self.stopped:
# Prevent calling bus.send from multiple threads
with self.send_lock:
- started = time.perf_counter()
try:
self.bus.send(self.messages[msg_index])
except Exception as exc: # pylint: disable=broad-except
@@ -294,13 +305,19 @@ def _run(self) -> None:
break
else:
break
+ msg_due_time_ns += self.period_ns
if self.end_time is not None and time.perf_counter() >= self.end_time:
break
msg_index = (msg_index + 1) % len(self.messages)
- if HAS_EVENTS:
- win32event.WaitForSingleObject(self.event.handle, self.period_ms)
- else:
- # Compensate for the time it takes to send the message
- delay = self.period - (time.perf_counter() - started)
- time.sleep(max(0.0, delay))
+ # Compensate for the time it takes to send the message
+ delay_ns = msg_due_time_ns - time.perf_counter_ns()
+
+ if delay_ns > 0:
+ if USE_WINDOWS_EVENTS:
+ win32event.WaitForSingleObject(
+ self.event.handle,
+ int(round(delay_ns / NANOSECONDS_IN_MILLISECOND)),
+ )
+ else:
+ time.sleep(delay_ns / NANOSECONDS_IN_SECOND)
From 740c50c275ef7ec6d867cc7557b055c6eb12dc84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tobias=20M=C3=BCller?=
Date: Thu, 30 Mar 2023 18:19:07 +0200
Subject: [PATCH 243/475] Improve support for TRC files (#1530)
* Improve support for TRC files
* Add support for Version 2.0
* Add support for $STARTTIME in Version 1.1 and Version 2.1
* Add support for $COLUMNS in Version 2.1
* Add type annotations
* Fix mypy findings
* remove assert
---------
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/io/trc.py | 114 +++++++++++++++++++++++++---------
test/data/test_CanMessage.trc | 2 +-
test/logformats_test.py | 16 +++--
3 files changed, 98 insertions(+), 34 deletions(-)
diff --git a/can/io/trc.py b/can/io/trc.py
index ec08d1af1..d1ee2b72d 100644
--- a/can/io/trc.py
+++ b/can/io/trc.py
@@ -7,15 +7,15 @@
Version 1.1 will be implemented as it is most commonly used
""" # noqa
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from enum import Enum
import io
import os
import logging
-from typing import Generator, Optional, Union, TextIO, Callable, List
+from typing import Generator, Optional, Union, TextIO, Callable, List, Dict
from ..message import Message
-from ..util import channel2int
+from ..util import channel2int, len2dlc, dlc2len
from .generic import FileIOMessageWriter, MessageReader
from ..typechecking import StringPathLike
@@ -32,6 +32,11 @@ class TRCFileVersion(Enum):
V2_0 = 200
V2_1 = 201
+ def __ge__(self, other):
+ if self.__class__ is other.__class__:
+ return self.value >= other.value
+ return NotImplemented
+
class TRCReader(MessageReader):
"""
@@ -51,6 +56,8 @@ def __init__(
"""
super().__init__(file, mode="r")
self.file_version = TRCFileVersion.UNKNOWN
+ self.start_time: Optional[datetime] = None
+ self.columns: Dict[str, int] = {}
if not self.file:
raise ValueError("The given file cannot be None")
@@ -67,17 +74,42 @@ def _extract_header(self):
file_version = line.split("=")[1]
if file_version == "1.1":
self.file_version = TRCFileVersion.V1_1
+ elif file_version == "2.0":
+ self.file_version = TRCFileVersion.V2_0
elif file_version == "2.1":
self.file_version = TRCFileVersion.V2_1
else:
self.file_version = TRCFileVersion.UNKNOWN
except IndexError:
logger.debug("TRCReader: Failed to parse version")
+ elif line.startswith(";$STARTTIME"):
+ logger.debug("TRCReader: Found start time '%s'", line)
+ try:
+ self.start_time = datetime(
+ 1899, 12, 30, tzinfo=timezone.utc
+ ) + timedelta(days=float(line.split("=")[1]))
+ except IndexError:
+ logger.debug("TRCReader: Failed to parse start time")
+ elif line.startswith(";$COLUMNS"):
+ logger.debug("TRCReader: Found columns '%s'", line)
+ try:
+ columns = line.split("=")[1].split(",")
+ self.columns = {column: columns.index(column) for column in columns}
+ except IndexError:
+ logger.debug("TRCReader: Failed to parse columns")
elif line.startswith(";"):
continue
else:
break
+ if self.file_version >= TRCFileVersion.V1_1:
+ if self.start_time is None:
+ raise ValueError("File has no start time information")
+
+ if self.file_version >= TRCFileVersion.V2_0:
+ if not self.columns:
+ raise ValueError("File has no column information")
+
if self.file_version == TRCFileVersion.UNKNOWN:
logger.info(
"TRCReader: No file version was found, so version 1.0 is assumed"
@@ -87,8 +119,8 @@ def _extract_header(self):
self._parse_cols = self._parse_msg_V1_0
elif self.file_version == TRCFileVersion.V1_1:
self._parse_cols = self._parse_cols_V1_1
- elif self.file_version == TRCFileVersion.V2_1:
- self._parse_cols = self._parse_cols_V2_1
+ elif self.file_version in [TRCFileVersion.V2_0, TRCFileVersion.V2_1]:
+ self._parse_cols = self._parse_cols_V2_x
else:
raise NotImplementedError("File version not fully implemented for reading")
@@ -113,7 +145,12 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]:
arbit_id = cols[3]
msg = Message()
- msg.timestamp = float(cols[1]) / 1000
+ if isinstance(self.start_time, datetime):
+ msg.timestamp = (
+ self.start_time + timedelta(milliseconds=float(cols[1]))
+ ).timestamp()
+ else:
+ msg.timestamp = float(cols[1]) / 1000
msg.arbitration_id = int(arbit_id, 16)
msg.is_extended_id = len(arbit_id) > 4
msg.channel = 1
@@ -122,15 +159,38 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]:
msg.is_rx = cols[2] == "Rx"
return msg
- def _parse_msg_V2_1(self, cols: List[str]) -> Optional[Message]:
+ def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]:
+ type_ = cols[self.columns["T"]]
+ bus = self.columns.get("B", None)
+
+ if "l" in self.columns:
+ length = int(cols[self.columns["l"]])
+ dlc = len2dlc(length)
+ elif "L" in self.columns:
+ dlc = int(cols[self.columns["L"]])
+ length = dlc2len(dlc)
+ else:
+ raise ValueError("No length/dlc columns present.")
+
msg = Message()
- msg.timestamp = float(cols[1]) / 1000
- msg.arbitration_id = int(cols[4], 16)
- msg.is_extended_id = len(cols[4]) > 4
- msg.channel = int(cols[3])
- msg.dlc = int(cols[7])
- msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)])
- msg.is_rx = cols[5] == "Rx"
+ if isinstance(self.start_time, datetime):
+ msg.timestamp = (
+ self.start_time + timedelta(milliseconds=float(cols[self.columns["O"]]))
+ ).timestamp()
+ else:
+ msg.timestamp = float(cols[1]) / 1000
+ msg.arbitration_id = int(cols[self.columns["I"]], 16)
+ msg.is_extended_id = len(cols[self.columns["I"]]) > 4
+ msg.channel = int(cols[bus]) if bus is not None else 1
+ msg.dlc = dlc
+ msg.data = bytearray(
+ [int(cols[i + self.columns["D"]], 16) for i in range(length)]
+ )
+ msg.is_rx = cols[self.columns["d"]] == "Rx"
+ msg.is_fd = type_ in ["FD", "FB", "FE", "BI"]
+ msg.bitrate_switch = type_ in ["FB", " FE"]
+ msg.error_state_indicator = type_ in ["FE", "BI"]
+
return msg
def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]:
@@ -141,10 +201,10 @@ def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]:
logger.info("TRCReader: Unsupported type '%s'", dtype)
return None
- def _parse_cols_V2_1(self, cols: List[str]) -> Optional[Message]:
- dtype = cols[2]
- if dtype == "DT":
- return self._parse_msg_V2_1(cols)
+ def _parse_cols_V2_x(self, cols: List[str]) -> Optional[Message]:
+ dtype = cols[self.columns["T"]]
+ if dtype in ["DT", "FD", "FB"]:
+ return self._parse_msg_V2_x(cols)
else:
logger.info("TRCReader: Unsupported type '%s'", dtype)
return None
@@ -228,7 +288,7 @@ def __init__(
self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0
self._format_message = self._format_message_init
- def _write_header_V1_0(self, start_time: timedelta) -> None:
+ def _write_header_V1_0(self, start_time: datetime) -> None:
lines = [
";##########################################################################",
f"; {self.filepath}",
@@ -249,13 +309,11 @@ def _write_header_V1_0(self, start_time: timedelta) -> None:
]
self.file.writelines(line + "\n" for line in lines)
- def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None:
- milliseconds = int(
- (header_time.seconds * 1000) + (header_time.microseconds / 1000)
- )
+ def _write_header_V2_1(self, start_time: datetime) -> None:
+ header_time = start_time - datetime(year=1899, month=12, day=30)
lines = [
";$FILEVERSION=2.1",
- f";$STARTTIME={header_time.days}.{milliseconds}",
+ f";$STARTTIME={header_time/timedelta(days=1)}",
";$COLUMNS=N,O,T,B,I,d,R,L,D",
";",
f"; {self.filepath}",
@@ -308,14 +366,12 @@ def _format_message_init(self, msg, channel):
def write_header(self, timestamp: float) -> None:
# write start of file header
- ref_time = datetime(year=1899, month=12, day=30)
- start_time = datetime.now() + timedelta(seconds=timestamp)
- header_time = start_time - ref_time
+ start_time = datetime.utcfromtimestamp(timestamp)
if self.file_version == TRCFileVersion.V1_0:
- self._write_header_V1_0(header_time)
+ self._write_header_V1_0(start_time)
elif self.file_version == TRCFileVersion.V2_1:
- self._write_header_V2_1(header_time, start_time)
+ self._write_header_V2_1(start_time)
else:
raise NotImplementedError("File format is not supported")
self.header_written = True
diff --git a/test/data/test_CanMessage.trc b/test/data/test_CanMessage.trc
index 215997b57..8b1361808 100644
--- a/test/data/test_CanMessage.trc
+++ b/test/data/test_CanMessage.trc
@@ -1,5 +1,5 @@
;$FILEVERSION=2.1
-;$STARTTIME=0
+;$STARTTIME=43008.920986006946
;$COLUMNS=N,O,T,B,I,d,R,L,D
;
; C:\Users\User\Desktop\python-can\test\data\test_CanMessage.trc
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 05c8b986f..3486827a9 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -810,9 +810,10 @@ class TestTrcFileFormatGen(TestTrcFileFormatBase):
"""Generic tests for can.TRCWriter and can.TRCReader with different file versions"""
def test_can_message(self):
+ start_time = 1506809173.191 # 30.09.2017 22:06:13.191.000 as timestamp
expected_messages = [
can.Message(
- timestamp=2.5010,
+ timestamp=start_time + 2.5010,
arbitration_id=0xC8,
is_extended_id=False,
is_rx=False,
@@ -821,7 +822,7 @@ def test_can_message(self):
data=[9, 8, 7, 6, 5, 4, 3, 2],
),
can.Message(
- timestamp=17.876708,
+ timestamp=start_time + 17.876708,
arbitration_id=0x6F9,
is_extended_id=False,
channel=0,
@@ -841,10 +842,17 @@ def test_can_message(self):
)
def test_can_message_versions(self, name, filename, is_rx_support):
with self.subTest(name):
+ if name == "V1_0":
+ # Version 1.0 does not support start time
+ start_time = 0
+ else:
+ start_time = (
+ 1639837687.062001 # 18.12.2021 14:28:07.062.001 as timestamp
+ )
def msg_std(timestamp):
msg = can.Message(
- timestamp=timestamp,
+ timestamp=timestamp + start_time,
arbitration_id=0x000,
is_extended_id=False,
channel=1,
@@ -857,7 +865,7 @@ def msg_std(timestamp):
def msg_ext(timestamp):
msg = can.Message(
- timestamp=timestamp,
+ timestamp=timestamp + start_time,
arbitration_id=0x100,
is_extended_id=True,
channel=1,
From 7855da1b4bb32f7bb3a1e733d59be40571fd4f8b Mon Sep 17 00:00:00 2001
From: Teejay
Date: Thu, 30 Mar 2023 09:58:18 -0700
Subject: [PATCH 244/475] Add `__del__` method to `BusABC` (#1489)
* Add del method
* Add unittest
* Satisfy black formatter
* Satisfy pylint linter
* PR feedback
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
* PR feedback
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
* Move to cls attribute
* Add unittest
* Call parent shutdown from socketcand
* Wrap del in try except
* Call parent shutdown from ixxat
* Black & pylint
* PR feedback
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
* Remove try/except & fix ordering
* Fix unittest
* Call parent shutdown from etas
* Add warning filter
* Make multicast_udp back2back test more specific
* clean up test_interface_canalystii.py
* carry over from #1519
* fix AttributeError
---------
Co-authored-by: TJ Bruno
Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com>
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
---
can/bus.py | 28 ++++++++++++++---
can/interfaces/canalystii.py | 21 +++++++------
can/interfaces/etas/__init__.py | 1 +
can/interfaces/ixxat/canlib.py | 1 +
can/interfaces/socketcand/socketcand.py | 2 +-
test/back2back_test.py | 7 +++--
test/test_bus.py | 9 ++++++
test/test_interface.py | 42 +++++++++++++++++++++++++
test/test_interface_canalystii.py | 6 +---
9 files changed, 94 insertions(+), 23 deletions(-)
create mode 100644 test/test_interface.py
diff --git a/can/bus.py b/can/bus.py
index 292c754fb..32a26ebcb 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -1,7 +1,7 @@
"""
Contains the ABC bus implementation and its documentation.
"""
-
+import contextlib
from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union
import can.typechecking
@@ -44,12 +44,14 @@ class BusABC(metaclass=ABCMeta):
#: Log level for received messages
RECV_LOGGING_LEVEL = 9
+ _is_shutdown: bool = False
+
@abstractmethod
def __init__(
self,
channel: Any,
can_filters: Optional[can.typechecking.CanFilters] = None,
- **kwargs: object
+ **kwargs: object,
):
"""Construct and open a CAN bus instance of the specified type.
@@ -301,6 +303,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None:
:param remove_tasks:
Stop tracking the stopped tasks.
"""
+ if not hasattr(self, "_periodic_tasks"):
+ # avoid AttributeError for partially initialized BusABC instance
+ return
+
for task in self._periodic_tasks:
# we cannot let `task.stop()` modify `self._periodic_tasks` while we are
# iterating over it (#634)
@@ -415,9 +421,15 @@ def flush_tx_buffer(self) -> None:
def shutdown(self) -> None:
"""
- Called to carry out any interface specific cleanup required
- in shutting down a bus.
+ Called to carry out any interface specific cleanup required in shutting down a bus.
+
+ This method can be safely called multiple times.
"""
+ if self._is_shutdown:
+ LOG.debug("%s is already shut down", self.__class__)
+ return
+
+ self._is_shutdown = True
self.stop_all_periodic_tasks()
def __enter__(self):
@@ -426,6 +438,14 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
self.shutdown()
+ def __del__(self) -> None:
+ if not self._is_shutdown:
+ LOG.warning("%s was not properly shut down", self.__class__)
+ # We do some best-effort cleanup if the user
+ # forgot to properly close the bus instance
+ with contextlib.suppress(AttributeError):
+ self.shutdown()
+
@property
def state(self) -> BusState:
"""
diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py
index 7150a60bd..1e6a7fea4 100644
--- a/can/interfaces/canalystii.py
+++ b/can/interfaces/canalystii.py
@@ -1,11 +1,11 @@
-import collections
+from collections import deque
from ctypes import c_ubyte
import logging
import time
from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union
from can import BitTiming, BusABC, Message, BitTimingFd
-from can.exceptions import CanTimeoutError, CanInitializationError
+from can.exceptions import CanTimeoutError
from can.typechecking import CanFilters
from can.util import deprecated_args_alias, check_or_adjust_timing_clock
@@ -50,11 +50,12 @@ def __init__(
If set, software received message queue can only grow to this many
messages (for all channels) before older messages are dropped
"""
- super().__init__(channel=channel, can_filters=can_filters, **kwargs)
-
if not (bitrate or timing):
raise ValueError("Either bitrate or timing argument is required")
+ # Do this after the error handling
+ super().__init__(channel=channel, can_filters=can_filters, **kwargs)
+
if isinstance(channel, str):
# Assume comma separated string of channels
self.channels = [int(ch.strip()) for ch in channel.split(",")]
@@ -63,23 +64,23 @@ def __init__(
else: # Sequence[int]
self.channels = list(channel)
- self.rx_queue = collections.deque(
- maxlen=rx_queue_size
- ) # type: Deque[Tuple[int, driver.Message]]
+ self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size)
self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}"
self.device = driver.CanalystDevice(device_index=device)
- for channel in self.channels:
+ for single_channel in self.channels:
if isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000])
- self.device.init(channel, timing0=timing.btr0, timing1=timing.btr1)
+ self.device.init(
+ single_channel, timing0=timing.btr0, timing1=timing.btr1
+ )
elif isinstance(timing, BitTimingFd):
raise NotImplementedError(
f"CAN FD is not supported by {self.__class__.__name__}."
)
else:
- self.device.init(channel, bitrate=bitrate)
+ self.device.init(single_channel, bitrate=bitrate)
# Delay to use between each poll for new messages
#
diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py
index 3a203a50d..03dc42bc1 100644
--- a/can/interfaces/etas/__init__.py
+++ b/can/interfaces/etas/__init__.py
@@ -248,6 +248,7 @@ def flush_tx_buffer(self) -> None:
OCI_ResetQueue(self.txQueue)
def shutdown(self) -> None:
+ super().shutdown()
# Cleanup TX
if self.txQueue:
OCI_DestroyCANTxQueue(self.txQueue)
diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py
index a20e4f59b..01f754115 100644
--- a/can/interfaces/ixxat/canlib.py
+++ b/can/interfaces/ixxat/canlib.py
@@ -147,6 +147,7 @@ def _send_periodic_internal(self, msgs, period, duration=None):
return self.bus._send_periodic_internal(msgs, period, duration)
def shutdown(self) -> None:
+ super().shutdown()
self.bus.shutdown()
@property
diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py
index 32b9a0edf..3f4e2ac86 100644
--- a/can/interfaces/socketcand/socketcand.py
+++ b/can/interfaces/socketcand/socketcand.py
@@ -183,5 +183,5 @@ def send(self, msg, timeout=None):
self._tcp_send(ascii_msg)
def shutdown(self):
- self.stop_all_periodic_tasks()
+ super().shutdown()
self.__socket.close()
diff --git a/test/back2back_test.py b/test/back2back_test.py
index ce09b6179..48c98bf59 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -12,6 +12,7 @@
import pytest
import can
+from can import CanInterfaceNotImplementedError
from can.interfaces.udp_multicast import UdpMulticastBus
from .config import (
@@ -294,7 +295,7 @@ class BasicTestUdpMulticastBusIPv4(Back2BackTestCase):
CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv4
def test_unique_message_instances(self):
- with self.assertRaises(NotImplementedError):
+ with self.assertRaises(CanInterfaceNotImplementedError):
super().test_unique_message_instances()
@@ -313,7 +314,7 @@ class BasicTestUdpMulticastBusIPv6(Back2BackTestCase):
CHANNEL_2 = HOST_LOCAL_MCAST_GROUP_IPv6
def test_unique_message_instances(self):
- with self.assertRaises(NotImplementedError):
+ with self.assertRaises(CanInterfaceNotImplementedError):
super().test_unique_message_instances()
@@ -321,7 +322,7 @@ def test_unique_message_instances(self):
try:
bus_class = can.interface._get_class_for_interface("etas")
TEST_INTERFACE_ETAS = True
-except can.exceptions.CanInterfaceNotImplementedError:
+except CanInterfaceNotImplementedError:
pass
diff --git a/test/test_bus.py b/test/test_bus.py
index e11d829d3..24421b2fd 100644
--- a/test/test_bus.py
+++ b/test/test_bus.py
@@ -1,3 +1,4 @@
+import gc
from unittest.mock import patch
import can
@@ -12,3 +13,11 @@ def test_bus_ignore_config():
_ = can.Bus(interface="virtual")
assert can.util.load_config.called
+
+
+@patch.object(can.bus.BusABC, "shutdown")
+def test_bus_attempts_self_cleanup(mock_shutdown):
+ bus = can.Bus(interface="virtual")
+ del bus
+ gc.collect()
+ mock_shutdown.assert_called()
diff --git a/test/test_interface.py b/test/test_interface.py
new file mode 100644
index 000000000..271e90b1b
--- /dev/null
+++ b/test/test_interface.py
@@ -0,0 +1,42 @@
+import importlib
+from unittest.mock import patch
+
+import pytest
+
+import can
+from can.interfaces import BACKENDS
+
+
+@pytest.fixture(params=(BACKENDS.keys()))
+def constructor(request):
+ mod, cls = BACKENDS[request.param]
+
+ try:
+ module = importlib.import_module(mod)
+ constructor = getattr(module, cls)
+ except:
+ pytest.skip("Unable to load interface")
+
+ return constructor
+
+
+@pytest.fixture
+def interface(constructor):
+ class MockInterface(constructor):
+ def __init__(self):
+ pass
+
+ def __del__(self):
+ pass
+
+ return MockInterface()
+
+
+@patch.object(can.bus.BusABC, "shutdown")
+def test_interface_calls_parent_shutdown(mock_shutdown, interface):
+ try:
+ interface.shutdown()
+ except:
+ pass
+ finally:
+ mock_shutdown.assert_called()
diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py
index 4d1d3eb84..4f3033e10 100755
--- a/test/test_interface_canalystii.py
+++ b/test/test_interface_canalystii.py
@@ -1,11 +1,7 @@
#!/usr/bin/env python
-"""
-"""
-
-import time
import unittest
-from unittest.mock import Mock, patch, call
+from unittest.mock import patch, call
from ctypes import c_ubyte
import canalystii as driver # low-level driver module, mock out this layer
From 1188c57a43be6bfa48490c4d2e5f3ea472a5e637 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sat, 1 Apr 2023 23:08:15 +0200
Subject: [PATCH 245/475] Export symbols to satisfy type checkers (#1551)
* use ruff with isort, export symbols according to PEP484
* fix CI
* fix CI
* use __all__
---
.github/workflows/ci.yml | 3 +
.pylintrc | 2 +
can/__init__.py | 115 +++++++++++++-----
can/bit_timing.py | 4 +-
can/broadcastmanager.py | 2 +-
can/bus.py | 15 ++-
can/ctypesutil.py | 1 -
can/exceptions.py | 4 +-
can/interface.py | 4 +-
can/interfaces/__init__.py | 2 +-
can/interfaces/canalystii.py | 14 +--
can/interfaces/cantact.py | 9 +-
can/interfaces/etas/__init__.py | 4 +-
can/interfaces/gs_usb.py | 10 +-
can/interfaces/ics_neovi/__init__.py | 12 +-
can/interfaces/ics_neovi/neovi_bus.py | 9 +-
can/interfaces/iscan.py | 7 +-
can/interfaces/ixxat/__init__.py | 11 +-
can/interfaces/ixxat/canlib.py | 5 +-
can/interfaces/ixxat/canlib_vcinpl.py | 9 +-
can/interfaces/ixxat/canlib_vcinpl2.py | 10 +-
can/interfaces/kvaser/canlib.py | 10 +-
can/interfaces/neousys/__init__.py | 2 +
can/interfaces/neousys/neousys.py | 15 ++-
can/interfaces/nican.py | 8 +-
can/interfaces/nixnet.py | 6 +-
can/interfaces/pcan/__init__.py | 5 +
can/interfaces/pcan/basic.py | 5 +-
can/interfaces/pcan/pcan.py | 86 +++++++------
can/interfaces/robotell.py | 3 +-
can/interfaces/seeedstudio/__init__.py | 2 +
can/interfaces/seeedstudio/seeedstudio.py | 2 +-
can/interfaces/serial/__init__.py | 4 +-
can/interfaces/serial/serial_can.py | 7 +-
can/interfaces/slcan.py | 12 +-
can/interfaces/socketcan/__init__.py | 8 +-
can/interfaces/socketcan/socketcan.py | 17 ++-
can/interfaces/socketcan/utils.py | 2 +-
can/interfaces/socketcand/__init__.py | 2 +
can/interfaces/socketcand/socketcand.py | 7 +-
can/interfaces/systec/__init__.py | 2 +
can/interfaces/systec/constants.py | 4 +-
can/interfaces/systec/exceptions.py | 4 +-
can/interfaces/systec/structures.py | 16 ++-
can/interfaces/systec/ucan.py | 4 +-
can/interfaces/systec/ucanbus.py | 4 +-
can/interfaces/udp_multicast/__init__.py | 2 +
can/interfaces/udp_multicast/bus.py | 3 +-
can/interfaces/udp_multicast/utils.py | 7 +-
can/interfaces/usb2can/__init__.py | 7 +-
can/interfaces/usb2can/usb2canInterface.py | 11 +-
.../usb2can/usb2canabstractionlayer.py | 3 +-
can/interfaces/vector/__init__.py | 20 ++-
can/interfaces/vector/canlib.py | 30 ++---
can/interfaces/vector/xldefine.py | 1 -
can/interfaces/vector/xldriver.py | 3 +-
can/interfaces/virtual.py | 9 +-
can/io/__init__.py | 34 +++++-
can/io/asc.py | 12 +-
can/io/blf.py | 11 +-
can/io/canutils.py | 5 +-
can/io/csv.py | 7 +-
can/io/generic.py | 38 +++---
can/io/logger.py | 15 ++-
can/io/player.py | 6 +-
can/io/printer.py | 5 +-
can/io/sqlite.py | 9 +-
can/io/trc.py | 13 +-
can/listener.py | 6 +-
can/logconvert.py | 4 +-
can/logger.py | 7 +-
can/message.py | 5 +-
can/notifier.py | 2 +-
can/player.py | 6 +-
can/thread_safe_bus.py | 1 -
can/util.py | 12 +-
can/viewer.py | 9 +-
doc/conf.py | 4 +-
pyproject.toml | 13 ++
requirements-lint.txt | 1 +
setup.py | 7 +-
test/back2back_test.py | 10 +-
test/contextmanager_test.py | 1 +
test/listener_test.py | 8 +-
test/logformats_test.py | 17 +--
test/network_test.py | 6 +-
test/notifier_test.py | 4 +-
test/serial_test.py | 3 +-
test/simplecyclic_test.py | 4 +-
test/test_cyclic_socketcan.py | 2 +-
test/test_interface_canalystii.py | 6 +-
test/test_interface_ixxat.py | 1 +
test/test_interface_ixxat_fd.py | 1 +
test/test_kvaser.py | 3 +-
test/test_logger.py | 6 +-
test/test_message_class.py | 13 +-
test/test_message_filtering.py | 1 -
test/test_message_sync.py | 13 +-
test/test_neousys.py | 12 +-
test/test_neovi.py | 1 +
test/test_player.py | 7 +-
test/test_robotell.py | 1 +
test/test_rotating_loggers.py | 1 +
test/test_scripts.py | 4 +-
test/test_slcan.py | 3 +-
test/test_socketcan.py | 7 +-
test/test_socketcan_helpers.py | 6 +-
test/test_util.py | 4 +-
test/test_vector.py | 14 ++-
test/test_viewer.py | 3 +-
test/zero_dlc_test.py | 3 +-
111 files changed, 567 insertions(+), 415 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4f96578e5..465d5959b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -95,6 +95,9 @@ jobs:
- name: mypy 3.11
run: |
mypy --python-version 3.11 .
+ - name: ruff
+ run: |
+ ruff check can
- name: pylint
run: |
pylint --rcfile=.pylintrc \
diff --git a/.pylintrc b/.pylintrc
index bdbf47613..de2e82a0d 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -438,6 +438,8 @@ known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
+# Allow explicit reexports by alias from a package __init__
+allow-reexport-from-package=no
[CLASSES]
diff --git a/can/__init__.py b/can/__init__.py
index d9ff5ffcd..5cc054c19 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -6,47 +6,104 @@
"""
import logging
-from typing import Dict, Any
+from typing import Any, Dict
__version__ = "4.1.0"
+__all__ = [
+ "ASCReader",
+ "ASCWriter",
+ "AsyncBufferedReader",
+ "BitTiming",
+ "BitTimingFd",
+ "BLFReader",
+ "BLFWriter",
+ "broadcastmanager",
+ "BufferedReader",
+ "Bus",
+ "BusABC",
+ "BusState",
+ "CanError",
+ "CanInitializationError",
+ "CanInterfaceNotImplementedError",
+ "CanOperationError",
+ "CanTimeoutError",
+ "CanutilsLogReader",
+ "CanutilsLogWriter",
+ "CSVReader",
+ "CSVWriter",
+ "CyclicSendTaskABC",
+ "detect_available_configs",
+ "interface",
+ "LimitedDurationCyclicSendTaskABC",
+ "Listener",
+ "Logger",
+ "LogReader",
+ "ModifiableCyclicTaskABC",
+ "Message",
+ "MessageSync",
+ "Notifier",
+ "Printer",
+ "RedirectReader",
+ "RestartableCyclicTaskABC",
+ "set_logging_level",
+ "SizedRotatingLogger",
+ "SqliteReader",
+ "SqliteWriter",
+ "ThreadSafeBus",
+ "typechecking",
+ "TRCFileVersion",
+ "TRCReader",
+ "TRCWriter",
+ "util",
+ "VALID_INTERFACES",
+]
log = logging.getLogger("can")
rc: Dict[str, Any] = {}
-from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader
-
+from . import typechecking # isort:skip
+from . import util # isort:skip
+from . import broadcastmanager, interface
+from .bit_timing import BitTiming, BitTimingFd
+from .broadcastmanager import (
+ CyclicSendTaskABC,
+ LimitedDurationCyclicSendTaskABC,
+ ModifiableCyclicTaskABC,
+ RestartableCyclicTaskABC,
+)
+from .bus import BusABC, BusState
from .exceptions import (
CanError,
- CanInterfaceNotImplementedError,
CanInitializationError,
+ CanInterfaceNotImplementedError,
CanOperationError,
CanTimeoutError,
)
-
-from .util import set_logging_level
-
-from .message import Message
-from .bus import BusABC, BusState
-from .thread_safe_bus import ThreadSafeBus
-from .notifier import Notifier
-from .interfaces import VALID_INTERFACES
-from . import interface
from .interface import Bus, detect_available_configs
-from .bit_timing import BitTiming, BitTimingFd
-
-from .io import Logger, SizedRotatingLogger, 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 .io import TRCReader, TRCWriter, TRCFileVersion
-
-from .broadcastmanager import (
- CyclicSendTaskABC,
- LimitedDurationCyclicSendTaskABC,
- ModifiableCyclicTaskABC,
- MultiRateCyclicSendTaskABC,
- RestartableCyclicTaskABC,
+from .interfaces import VALID_INTERFACES
+from .io import (
+ ASCReader,
+ ASCWriter,
+ BLFReader,
+ BLFWriter,
+ CanutilsLogReader,
+ CanutilsLogWriter,
+ CSVReader,
+ CSVWriter,
+ Logger,
+ LogReader,
+ MessageSync,
+ Printer,
+ SizedRotatingLogger,
+ SqliteReader,
+ SqliteWriter,
+ TRCFileVersion,
+ TRCReader,
+ TRCWriter,
)
+from .listener import AsyncBufferedReader, BufferedReader, Listener, RedirectReader
+from .message import Message
+from .notifier import Notifier
+from .thread_safe_bus import ThreadSafeBus
+from .util import set_logging_level
diff --git a/can/bit_timing.py b/can/bit_timing.py
index 4fada145b..bf76c08af 100644
--- a/can/bit_timing.py
+++ b/can/bit_timing.py
@@ -1,8 +1,8 @@
# pylint: disable=too-many-lines
import math
-from typing import List, Mapping, Iterator, cast
+from typing import Iterator, List, Mapping, cast
-from can.typechecking import BitTimingFdDict, BitTimingDict
+from can.typechecking import BitTimingDict, BitTimingFdDict
class BitTiming(Mapping):
diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py
index d795eb1fa..07d93e296 100644
--- a/can/broadcastmanager.py
+++ b/can/broadcastmanager.py
@@ -10,7 +10,7 @@
import sys
import threading
import time
-from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING
+from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
from typing_extensions import Final
diff --git a/can/bus.py b/can/bus.py
index 32a26ebcb..3964215d3 100644
--- a/can/bus.py
+++ b/can/bus.py
@@ -1,19 +1,18 @@
"""
Contains the ABC bus implementation and its documentation.
"""
-import contextlib
-from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union
-
-import can.typechecking
-from abc import ABC, ABCMeta, abstractmethod
-import can
+import contextlib
import logging
import threading
-from time import time
+from abc import ABC, ABCMeta, abstractmethod
from enum import Enum, auto
+from time import time
+from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union, cast
-from can.broadcastmanager import ThreadBasedCyclicSendTask, CyclicSendTaskABC
+import can
+import can.typechecking
+from can.broadcastmanager import CyclicSendTaskABC, ThreadBasedCyclicSendTask
from can.message import Message
LOG = logging.getLogger(__name__)
diff --git a/can/ctypesutil.py b/can/ctypesutil.py
index 4cfebb5b8..e624db6d2 100644
--- a/can/ctypesutil.py
+++ b/can/ctypesutil.py
@@ -5,7 +5,6 @@
import ctypes
import logging
import sys
-
from typing import Any, Callable, Optional, Tuple, Union
log = logging.getLogger("can.ctypesutil")
diff --git a/can/exceptions.py b/can/exceptions.py
index 57130082a..e1c970e27 100644
--- a/can/exceptions.py
+++ b/can/exceptions.py
@@ -17,9 +17,7 @@
import sys
from contextlib import contextmanager
-
-from typing import Optional
-from typing import Type
+from typing import Optional, Type
if sys.version_info >= (3, 9):
from collections.abc import Generator
diff --git a/can/interface.py b/can/interface.py
index 47b44fed1..4c59dab8b 100644
--- a/can/interface.py
+++ b/can/interface.py
@@ -6,12 +6,12 @@
import importlib
import logging
-from typing import Any, cast, Iterable, Type, Optional, Union, List
+from typing import Any, Iterable, List, Optional, Type, Union, cast
from . import util
from .bus import BusABC
-from .interfaces import BACKENDS
from .exceptions import CanInterfaceNotImplementedError
+from .interfaces import BACKENDS
from .typechecking import AutoDetectedConfig, Channel
log = logging.getLogger("can.interface")
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index c9ca6ca55..5089dbf47 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -3,7 +3,7 @@
"""
import sys
-from typing import cast, Dict, Tuple
+from typing import Dict, Tuple, cast
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, str]] = {
diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py
index 1e6a7fea4..dd65f5ba0 100644
--- a/can/interfaces/canalystii.py
+++ b/can/interfaces/canalystii.py
@@ -1,15 +1,15 @@
-from collections import deque
-from ctypes import c_ubyte
import logging
import time
-from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union
+from collections import deque
+from ctypes import c_ubyte
+from typing import Any, Deque, Dict, Optional, Sequence, Tuple, Union
-from can import BitTiming, BusABC, Message, BitTimingFd
+import canalystii as driver
+
+from can import BitTiming, BitTimingFd, BusABC, Message
from can.exceptions import CanTimeoutError
from can.typechecking import CanFilters
-from can.util import deprecated_args_alias, check_or_adjust_timing_clock
-
-import canalystii as driver
+from can.util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py
index 20e4d0cb7..75e1adbaa 100644
--- a/can/interfaces/cantact.py
+++ b/can/interfaces/cantact.py
@@ -2,18 +2,19 @@
Interface for CANtact devices from Linklayer Labs
"""
-import time
import logging
-from typing import Optional, Union, Any
+import time
+from typing import Any, Optional, Union
from unittest.mock import Mock
-from can import BusABC, Message, BitTiming, BitTimingFd
+from can import BitTiming, BitTimingFd, BusABC, Message
+
from ..exceptions import (
CanInitializationError,
CanInterfaceNotImplementedError,
error_check,
)
-from ..util import deprecated_args_alias, check_or_adjust_timing_clock
+from ..util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py
index 03dc42bc1..5f768b3e5 100644
--- a/can/interfaces/etas/__init__.py
+++ b/can/interfaces/etas/__init__.py
@@ -3,7 +3,8 @@
from typing import Dict, List, Optional, Tuple
import can
-from ...exceptions import CanInitializationError
+from can.exceptions import CanInitializationError
+
from .boa import *
@@ -290,6 +291,7 @@ def state(self, new_state: can.BusState) -> None:
# raise CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}")
raise NotImplementedError("Setting state is not implemented.")
+ @staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX)
tree = ctypes.POINTER(CSI_Tree)()
diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py
index 185d28acf..fb5ce1d80 100644
--- a/can/interfaces/gs_usb.py
+++ b/can/interfaces/gs_usb.py
@@ -1,15 +1,15 @@
+import logging
from typing import Optional, Tuple
+import usb
+from gs_usb.constants import CAN_EFF_FLAG, CAN_ERR_FLAG, CAN_MAX_DLC, CAN_RTR_FLAG
from gs_usb.gs_usb import GsUsb
-from gs_usb.gs_usb_frame import GsUsbFrame, GS_USB_NONE_ECHO_ID
-from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC
+from gs_usb.gs_usb_frame import GS_USB_NONE_ECHO_ID, GsUsbFrame
+
import can
-import usb
-import logging
from ..exceptions import CanInitializationError, CanOperationError
-
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py
index 548e3ea3f..e221e0840 100644
--- a/can/interfaces/ics_neovi/__init__.py
+++ b/can/interfaces/ics_neovi/__init__.py
@@ -1,7 +1,11 @@
"""
"""
-from .neovi_bus import NeoViBus
-from .neovi_bus import ICSApiError
-from .neovi_bus import ICSInitializationError
-from .neovi_bus import ICSOperationError
+__all__ = [
+ "ICSApiError",
+ "ICSInitializationError",
+ "ICSOperationError",
+ "NeoViBus",
+]
+
+from .neovi_bus import ICSApiError, ICSInitializationError, ICSOperationError, NeoViBus
diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py
index c3abc9f67..848cefcc8 100644
--- a/can/interfaces/ics_neovi/neovi_bus.py
+++ b/can/interfaces/ics_neovi/neovi_bus.py
@@ -11,17 +11,18 @@
import logging
import os
import tempfile
-from collections import deque, defaultdict, Counter
+from collections import Counter, defaultdict, deque
from itertools import cycle
from threading import Event
from warnings import warn
-from can import Message, BusABC
+from can import BusABC, Message
+
from ...exceptions import (
CanError,
- CanTimeoutError,
- CanOperationError,
CanInitializationError,
+ CanOperationError,
+ CanTimeoutError,
)
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py
index a27494b61..ef3a48215 100644
--- a/can/interfaces/iscan.py
+++ b/can/interfaces/iscan.py
@@ -3,16 +3,17 @@
"""
import ctypes
-import time
import logging
+import time
from typing import Optional, Tuple, Union
-from can import BusABC, Message
from can import (
+ BusABC,
CanError,
- CanInterfaceNotImplementedError,
CanInitializationError,
+ CanInterfaceNotImplementedError,
CanOperationError,
+ Message,
)
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py
index 1419d97a1..bc871b372 100644
--- a/can/interfaces/ixxat/__init__.py
+++ b/can/interfaces/ixxat/__init__.py
@@ -4,7 +4,12 @@
Copyright (C) 2016-2021 Giuseppe Corbelli
"""
+__all__ = [
+ "get_ixxat_hwids",
+ "IXXATBus",
+]
+
from can.interfaces.ixxat.canlib import IXXATBus
-from can.interfaces.ixxat.canlib_vcinpl import (
- get_ixxat_hwids,
-) # import this and not the one from vcinpl2 for backward compatibility
+
+# import this and not the one from vcinpl2 for backward compatibility
+from can.interfaces.ixxat.canlib_vcinpl import get_ixxat_hwids
diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py
index 01f754115..3db719f96 100644
--- a/can/interfaces/ixxat/canlib.py
+++ b/can/interfaces/ixxat/canlib.py
@@ -1,11 +1,10 @@
+from typing import Optional
+
import can.interfaces.ixxat.canlib_vcinpl as vcinpl
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
-
from can import BusABC, Message
from can.bus import BusState
-from typing import Optional
-
class IXXATBus(BusABC):
"""The CAN Bus implemented for the IXXAT interface.
diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py
index 8304a6dd7..550484f3e 100644
--- a/can/interfaces/ixxat/canlib_vcinpl.py
+++ b/can/interfaces/ixxat/canlib_vcinpl.py
@@ -13,16 +13,17 @@
import functools
import logging
import sys
-from typing import Optional, Callable, Tuple
+from typing import Callable, Optional, Tuple
from can import BusABC, Message
-from can.bus import BusState
-from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError
from can.broadcastmanager import (
LimitedDurationCyclicSendTaskABC,
RestartableCyclicTaskABC,
)
-from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT
+from can.bus import BusState
+from can.ctypesutil import HANDLE, PHANDLE, CLibrary
+from can.ctypesutil import HRESULT as ctypes_HRESULT
+from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
from can.util import deprecated_args_alias
from . import constants, structures
diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py
index b8ed916dc..3e7f2ff91 100644
--- a/can/interfaces/ixxat/canlib_vcinpl2.py
+++ b/can/interfaces/ixxat/canlib_vcinpl2.py
@@ -13,17 +13,17 @@
import functools
import logging
import sys
-from typing import Optional, Callable, Tuple
+from typing import Callable, Optional, Tuple
+import can.util
from can import BusABC, Message
-from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError
from can.broadcastmanager import (
LimitedDurationCyclicSendTaskABC,
RestartableCyclicTaskABC,
)
-from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT
-
-import can.util
+from can.ctypesutil import HANDLE, PHANDLE, CLibrary
+from can.ctypesutil import HRESULT as ctypes_HRESULT
+from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
from can.util import deprecated_args_alias
from . import constants, structures
diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py
index 2bbf8f0bf..f6b92ccef 100644
--- a/can/interfaces/kvaser/canlib.py
+++ b/can/interfaces/kvaser/canlib.py
@@ -6,15 +6,15 @@
Copyright (C) 2010 Dynamic Controls
"""
+import ctypes
+import logging
import sys
import time
-import logging
-import ctypes
-from can import BusABC
-from ...exceptions import CanError, CanInitializationError, CanOperationError
-from can import Message
+from can import BusABC, Message
from can.util import time_perfcounter_correlation
+
+from ...exceptions import CanError, CanInitializationError, CanOperationError
from . import constants as canstat
from . import structures
diff --git a/can/interfaces/neousys/__init__.py b/can/interfaces/neousys/__init__.py
index 3aa87332c..6bd503f35 100644
--- a/can/interfaces/neousys/__init__.py
+++ b/can/interfaces/neousys/__init__.py
@@ -1,3 +1,5 @@
""" Neousys CAN bus driver """
+__all__ = ["NeousysBus"]
+
from can.interfaces.neousys.neousys import NeousysBus
diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py
index 57f947aa4..d57234ddd 100644
--- a/can/interfaces/neousys/neousys.py
+++ b/can/interfaces/neousys/neousys.py
@@ -14,21 +14,20 @@
# pylint: disable=too-many-instance-attributes
# pylint: disable=wrong-import-position
-import queue
import logging
import platform
-from time import time
-
+import queue
from ctypes import (
- byref,
CFUNCTYPE,
+ POINTER,
+ Structure,
+ byref,
c_ubyte,
c_uint,
c_ushort,
- POINTER,
sizeof,
- Structure,
)
+from time import time
try:
from ctypes import WinDLL
@@ -36,13 +35,13 @@
from ctypes import CDLL
from can import BusABC, Message
+
from ...exceptions import (
CanInitializationError,
- CanOperationError,
CanInterfaceNotImplementedError,
+ CanOperationError,
)
-
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py
index ea13e28e8..8457a1a38 100644
--- a/can/interfaces/nican.py
+++ b/can/interfaces/nican.py
@@ -16,17 +16,17 @@
import ctypes
import logging
import sys
+from typing import Optional, Tuple, Type
-from can import BusABC, Message
import can.typechecking
+from can import BusABC, Message
+
from ..exceptions import (
CanError,
+ CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
- CanInitializationError,
)
-from typing import Optional, Tuple, Type
-
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py
index 6c3e63697..4ddb52455 100644
--- a/can/interfaces/nixnet.py
+++ b/can/interfaces/nixnet.py
@@ -13,14 +13,14 @@
import time
from queue import SimpleQueue
from types import ModuleType
-from typing import Optional, List, Union, Tuple, Any
+from typing import Any, List, Optional, Tuple, Union
import can.typechecking
-from can import BusABC, Message, BitTiming, BitTimingFd
+from can import BitTiming, BitTimingFd, BusABC, Message
from can.exceptions import (
CanInitializationError,
- CanOperationError,
CanInterfaceNotImplementedError,
+ CanOperationError,
)
from can.util import check_or_adjust_timing_clock, deprecated_args_alias
diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py
index 0f28b0ffe..b46ccb050 100644
--- a/can/interfaces/pcan/__init__.py
+++ b/can/interfaces/pcan/__init__.py
@@ -1,4 +1,9 @@
"""
"""
+__all__ = [
+ "PcanBus",
+ "PcanError",
+]
+
from can.interfaces.pcan.pcan import PcanBus, PcanError
diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py
index d1b2121cb..a60b96079 100644
--- a/can/interfaces/pcan/basic.py
+++ b/can/interfaces/pcan/basic.py
@@ -15,11 +15,10 @@
# more Info at http://www.peak-system.com
# Module Imports
+import logging
+import platform
from ctypes import *
from ctypes.util import find_library
-import platform
-
-import logging
PLATFORM = platform.system()
IS_WINDOWS = PLATFORM == "Windows"
diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py
index 2ed0ff445..61d19d1b4 100644
--- a/can/interfaces/pcan/pcan.py
+++ b/can/interfaces/pcan/pcan.py
@@ -2,74 +2,74 @@
Enable basic CAN over a PCAN USB device.
"""
import logging
+import platform
import time
from datetime import datetime
-import platform
-from typing import Optional, List, Tuple, Union, Any
+from typing import Any, List, Optional, Tuple, Union
from packaging import version
from can import (
- BusABC,
- BusState,
BitTiming,
BitTimingFd,
- Message,
+ BusABC,
+ BusState,
CanError,
- CanOperationError,
CanInitializationError,
+ CanOperationError,
+ Message,
)
from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc
+
from .basic import (
- PCAN_BITRATES,
- PCAN_FD_PARAMETER_LIST,
- PCAN_CHANNEL_NAMES,
- PCAN_NONEBUS,
- PCAN_BAUD_500K,
- PCAN_TYPE_ISA,
- PCANBasic,
- PCAN_ERROR_OK,
+ FEATURE_FD_CAPABLE,
+ IS_LINUX,
+ IS_WINDOWS,
PCAN_ALLOW_ERROR_FRAMES,
- PCAN_PARAMETER_ON,
- PCAN_RECEIVE_EVENT,
PCAN_API_VERSION,
+ PCAN_ATTACHED_CHANNELS,
+ PCAN_BAUD_500K,
+ PCAN_BITRATES,
+ PCAN_BUSOFF_AUTORESET,
+ PCAN_CHANNEL_AVAILABLE,
+ PCAN_CHANNEL_CONDITION,
+ PCAN_CHANNEL_FEATURES,
+ PCAN_CHANNEL_IDENTIFYING,
+ PCAN_CHANNEL_NAMES,
PCAN_DEVICE_NUMBER,
- PCAN_ERROR_QRCVEMPTY,
- PCAN_ERROR_BUSLIGHT,
+ PCAN_DICT_STATUS,
PCAN_ERROR_BUSHEAVY,
- PCAN_MESSAGE_EXTENDED,
- PCAN_MESSAGE_RTR,
- PCAN_MESSAGE_FD,
+ PCAN_ERROR_BUSLIGHT,
+ PCAN_ERROR_OK,
+ PCAN_ERROR_QRCVEMPTY,
+ PCAN_FD_PARAMETER_LIST,
+ PCAN_LANBUS1,
+ PCAN_LISTEN_ONLY,
PCAN_MESSAGE_BRS,
- PCAN_MESSAGE_ESI,
PCAN_MESSAGE_ERRFRAME,
+ PCAN_MESSAGE_ESI,
+ PCAN_MESSAGE_EXTENDED,
+ PCAN_MESSAGE_FD,
+ PCAN_MESSAGE_RTR,
PCAN_MESSAGE_STANDARD,
- TPCANMsgFD,
- TPCANMsg,
- PCAN_CHANNEL_IDENTIFYING,
- PCAN_LISTEN_ONLY,
+ PCAN_NONEBUS,
PCAN_PARAMETER_OFF,
- TPCANHandle,
- IS_LINUX,
- IS_WINDOWS,
+ PCAN_PARAMETER_ON,
+ PCAN_PCCBUS1,
PCAN_PCIBUS1,
+ PCAN_RECEIVE_EVENT,
+ PCAN_TYPE_ISA,
PCAN_USBBUS1,
- PCAN_PCCBUS1,
- PCAN_LANBUS1,
- PCAN_CHANNEL_CONDITION,
- PCAN_CHANNEL_AVAILABLE,
- PCAN_CHANNEL_FEATURES,
- FEATURE_FD_CAPABLE,
- PCAN_DICT_STATUS,
- PCAN_BUSOFF_AUTORESET,
+ VALID_PCAN_CAN_CLOCKS,
+ VALID_PCAN_FD_CLOCKS,
+ PCANBasic,
TPCANBaudrate,
- PCAN_ATTACHED_CHANNELS,
TPCANChannelInformation,
- VALID_PCAN_FD_CLOCKS,
- VALID_PCAN_CAN_CLOCKS,
+ TPCANHandle,
+ TPCANMsg,
+ TPCANMsgFD,
)
-
# Set up logging
log = logging.getLogger("can.pcan")
@@ -96,7 +96,7 @@
try:
# Try builtin Python 3 Windows API
from _overlapped import CreateEvent
- from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
+ from _winapi import INFINITE, WAIT_OBJECT_0, WaitForSingleObject
HAS_EVENTS = True
except ImportError:
@@ -104,8 +104,6 @@
elif IS_LINUX:
try:
- import errno
- import os
import select
HAS_EVENTS = True
diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py
index 4d82c1922..4d038a38a 100644
--- a/can/interfaces/robotell.py
+++ b/can/interfaces/robotell.py
@@ -3,11 +3,12 @@
"""
import io
-import time
import logging
+import time
from typing import Optional
from can import BusABC, Message
+
from ..exceptions import CanInterfaceNotImplementedError, CanOperationError
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py
index cb1c17f1d..2fc348a17 100644
--- a/can/interfaces/seeedstudio/__init__.py
+++ b/can/interfaces/seeedstudio/__init__.py
@@ -1,4 +1,6 @@
"""
"""
+__all__ = ["SeeedBus"]
+
from can.interfaces.seeedstudio.seeedstudio import SeeedBus
diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py
index 4d09ca0cd..7d7a1e687 100644
--- a/can/interfaces/seeedstudio/seeedstudio.py
+++ b/can/interfaces/seeedstudio/seeedstudio.py
@@ -6,9 +6,9 @@
SKU 114991193
"""
+import io
import logging
import struct
-import io
from time import time
import can
diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py
index bd6a45b9c..1b1d63c49 100644
--- a/can/interfaces/serial/__init__.py
+++ b/can/interfaces/serial/__init__.py
@@ -1,4 +1,6 @@
"""
"""
-from can.interfaces.serial.serial_can import SerialBus as Bus
+__all__ = ["SerialBus"]
+
+from can.interfaces.serial.serial_can import SerialBus
diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py
index d0df88fcd..eb336feba 100644
--- a/can/interfaces/serial/serial_can.py
+++ b/can/interfaces/serial/serial_can.py
@@ -10,14 +10,15 @@
import io
import logging
import struct
-from typing import Any, List, Tuple, Optional
+from typing import Any, List, Optional, Tuple
-from can import BusABC, Message
from can import (
- CanInterfaceNotImplementedError,
+ BusABC,
CanInitializationError,
+ CanInterfaceNotImplementedError,
CanOperationError,
CanTimeoutError,
+ Message,
)
from can.typechecking import AutoDetectedConfig
diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py
index eed67a5f8..21306f28f 100644
--- a/can/interfaces/slcan.py
+++ b/can/interfaces/slcan.py
@@ -2,21 +2,19 @@
Interface for slcan compatible interfaces (win32/linux).
"""
-from typing import Any, Optional, Tuple
-
import io
-import time
import logging
+import time
+from typing import Any, Optional, Tuple
+
+from can import BusABC, Message, typechecking
-from can import BusABC, Message
from ..exceptions import (
- CanInterfaceNotImplementedError,
CanInitializationError,
+ CanInterfaceNotImplementedError,
CanOperationError,
error_check,
)
-from can import typechecking
-
logger = logging.getLogger(__name__)
diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py
index e08c18f50..0fbdede58 100644
--- a/can/interfaces/socketcan/__init__.py
+++ b/can/interfaces/socketcan/__init__.py
@@ -2,4 +2,10 @@
See: https://www.kernel.org/doc/Documentation/networking/can.txt
"""
-from .socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask
+__all__ = [
+ "CyclicSendTask",
+ "MultiRateCyclicSendTask",
+ "SocketcanBus",
+]
+
+from .socketcan import CyclicSendTask, MultiRateCyclicSendTask, SocketcanBus
diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py
index 74fbe8197..bdf39f0ab 100644
--- a/can/interfaces/socketcan/socketcan.py
+++ b/can/interfaces/socketcan/socketcan.py
@@ -5,17 +5,16 @@
At the end of the file the usage of the internal methods is shown.
"""
-from typing import Dict, List, Optional, Sequence, Tuple, Type, Union
-
-import logging
import ctypes
import ctypes.util
+import errno
+import logging
import select
import socket
import struct
-import time
import threading
-import errno
+import time
+from typing import Dict, List, Optional, Sequence, Tuple, Type, Union
log = logging.getLogger(__name__)
log_tx = log.getChild("tx")
@@ -31,15 +30,15 @@
import can
-from can import Message, BusABC
+from can import BusABC, Message
from can.broadcastmanager import (
+ LimitedDurationCyclicSendTaskABC,
ModifiableCyclicTaskABC,
RestartableCyclicTaskABC,
- LimitedDurationCyclicSendTaskABC,
)
-from can.typechecking import CanFilters
from can.interfaces.socketcan import constants
-from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces
+from can.interfaces.socketcan.utils import find_available_interfaces, pack_filters
+from can.typechecking import CanFilters
# Setup BCM struct
diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py
index 7a8538135..8b2114692 100644
--- a/can/interfaces/socketcan/utils.py
+++ b/can/interfaces/socketcan/utils.py
@@ -8,7 +8,7 @@
import os
import struct
import subprocess
-from typing import cast, Optional, List
+from typing import List, Optional, cast
from can import typechecking
from can.interfaces.socketcan.constants import CAN_EFF_FLAG
diff --git a/can/interfaces/socketcand/__init__.py b/can/interfaces/socketcand/__init__.py
index 442c06d8b..e6b106918 100644
--- a/can/interfaces/socketcand/__init__.py
+++ b/can/interfaces/socketcand/__init__.py
@@ -6,4 +6,6 @@
http://www.domologic.de
"""
+__all__ = ["SocketCanDaemonBus"]
+
from .socketcand import SocketCanDaemonBus
diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py
index 3f4e2ac86..0c6c06ccf 100644
--- a/can/interfaces/socketcand/socketcand.py
+++ b/can/interfaces/socketcand/socketcand.py
@@ -7,14 +7,15 @@
Copyright (C) 2021 DOMOLOGIC GmbH
http://www.domologic.de
"""
-import can
-import socket
-import select
import logging
+import select
+import socket
import time
import traceback
from collections import deque
+import can
+
log = logging.getLogger(__name__)
diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py
index be7004b6a..9a97b3054 100644
--- a/can/interfaces/systec/__init__.py
+++ b/can/interfaces/systec/__init__.py
@@ -1 +1,3 @@
+__all__ = ["UcanBus"]
+
from can.interfaces.systec.ucanbus import UcanBus
diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py
index 96952c17e..8caf9eab4 100644
--- a/can/interfaces/systec/constants.py
+++ b/can/interfaces/systec/constants.py
@@ -1,4 +1,6 @@
-from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD
+from ctypes import c_ubyte as BYTE
+from ctypes import c_ulong as DWORD
+from ctypes import c_ushort as WORD
#: Maximum number of modules that are supported.
MAX_MODULES = 64
diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py
index 733326194..9f7d4e2e5 100644
--- a/can/interfaces/systec/exceptions.py
+++ b/can/interfaces/systec/exceptions.py
@@ -1,9 +1,9 @@
+from abc import ABC, abstractmethod
from typing import Dict
-from abc import ABC, abstractmethod
+from can import CanError
from .constants import ReturnCode
-from can import CanError
class UcanException(CanError, ABC):
diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py
index 841474b80..699763989 100644
--- a/can/interfaces/systec/structures.py
+++ b/can/interfaces/systec/structures.py
@@ -1,12 +1,20 @@
-from ctypes import Structure, POINTER, sizeof
+import os
+from ctypes import POINTER, Structure, sizeof
+from ctypes import (
+ c_long as BOOL,
+)
from ctypes import (
c_ubyte as BYTE,
- c_ushort as WORD,
+)
+from ctypes import (
c_ulong as DWORD,
- c_long as BOOL,
+)
+from ctypes import (
+ c_ushort as WORD,
+)
+from ctypes import (
c_void_p as LPVOID,
)
-import os
# Workaround for Unix based platforms to be able to load structures for testing, etc...
if os.name == "nt":
diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py
index a6de4e9f5..bbc484314 100644
--- a/can/interfaces/systec/ucan.py
+++ b/can/interfaces/systec/ucan.py
@@ -1,14 +1,12 @@
import logging
import sys
-
from ctypes import byref
from ctypes import c_wchar_p as LPWSTR
from ...exceptions import CanInterfaceNotImplementedError
-
from .constants import *
-from .structures import *
from .exceptions import *
+from .structures import *
log = logging.getLogger("can.systec")
diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py
index 7d8b6133a..da05b38b1 100644
--- a/can/interfaces/systec/ucanbus.py
+++ b/can/interfaces/systec/ucanbus.py
@@ -2,11 +2,11 @@
from threading import Event
from can import BusABC, BusState, Message
-from ...exceptions import CanError, CanInitializationError, CanOperationError
+from ...exceptions import CanError, CanInitializationError, CanOperationError
from .constants import *
-from .structures import *
from .exceptions import UcanException
+from .structures import *
from .ucan import UcanServer
log = logging.getLogger("can.systec")
diff --git a/can/interfaces/udp_multicast/__init__.py b/can/interfaces/udp_multicast/__init__.py
index 0ce1ce389..6e11a02c5 100644
--- a/can/interfaces/udp_multicast/__init__.py
+++ b/can/interfaces/udp_multicast/__init__.py
@@ -1,3 +1,5 @@
"""A module to allow CAN over UDP on IPv4/IPv6 multicast."""
+__all__ = ["UdpMulticastBus"]
+
from .bus import UdpMulticastBus
diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py
index 5c7bee3e8..00cbd32c8 100644
--- a/can/interfaces/udp_multicast/bus.py
+++ b/can/interfaces/udp_multicast/bus.py
@@ -17,8 +17,7 @@
from can import BusABC
from can.typechecking import AutoDetectedConfig
-from .utils import pack_message, unpack_message, check_msgpack_installed
-
+from .utils import check_msgpack_installed, pack_message, unpack_message
# see socket.getaddrinfo()
IPv4_ADDRESS_INFO = Tuple[str, int] # address, port
diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py
index 2658bf0a9..35a0df185 100644
--- a/can/interfaces/udp_multicast/utils.py
+++ b/can/interfaces/udp_multicast/utils.py
@@ -2,12 +2,9 @@
Defines common functions.
"""
-from typing import Any
-from typing import Dict
-from typing import Optional
+from typing import Any, Dict, Optional
-from can import Message
-from can import CanInterfaceNotImplementedError
+from can import CanInterfaceNotImplementedError, Message
from can.typechecking import ReadableBytesLike
try:
diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py
index 4ccff1cb0..17f5583f3 100644
--- a/can/interfaces/usb2can/__init__.py
+++ b/can/interfaces/usb2can/__init__.py
@@ -1,5 +1,10 @@
"""
"""
-from .usb2canInterface import Usb2canBus
+__all__ = [
+ "Usb2CanAbstractionLayer",
+ "Usb2canBus",
+]
+
from .usb2canabstractionlayer import Usb2CanAbstractionLayer
+from .usb2canInterface import Usb2canBus
diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py
index bca40f8d3..2c0a0d00f 100644
--- a/can/interfaces/usb2can/usb2canInterface.py
+++ b/can/interfaces/usb2can/usb2canInterface.py
@@ -6,14 +6,17 @@
from ctypes import byref
from typing import Optional
-from can import BusABC, Message, CanInitializationError, CanOperationError
-from .usb2canabstractionlayer import Usb2CanAbstractionLayer, CanalMsg, CanalError
+from can import BusABC, CanInitializationError, CanOperationError, Message
+
+from .serial_selector import find_serial_devices
from .usb2canabstractionlayer import (
IS_ERROR_FRAME,
- IS_REMOTE_FRAME,
IS_ID_TYPE,
+ IS_REMOTE_FRAME,
+ CanalError,
+ CanalMsg,
+ Usb2CanAbstractionLayer,
)
-from .serial_selector import find_serial_devices
# Set up logging
log = logging.getLogger("can.usb2can")
diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py
index a6708cb42..a894c3953 100644
--- a/can/interfaces/usb2can/usb2canabstractionlayer.py
+++ b/can/interfaces/usb2can/usb2canabstractionlayer.py
@@ -3,11 +3,12 @@
Socket CAN is recommended under Unix/Linux systems.
"""
+import logging
from ctypes import *
from enum import IntEnum
-import logging
import can
+
from ...exceptions import error_check
from ...typechecking import StringPathLike
diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py
index c5eae7140..e0c34d88f 100644
--- a/can/interfaces/vector/__init__.py
+++ b/can/interfaces/vector/__init__.py
@@ -1,12 +1,24 @@
"""
"""
+__all__ = [
+ "get_channel_configs",
+ "VectorBus",
+ "VectorBusParams",
+ "VectorCanFdParams",
+ "VectorCanParams",
+ "VectorChannelConfig",
+ "VectorError",
+ "VectorInitializationError",
+ "VectorOperationError",
+]
+
from .canlib import (
VectorBus,
- get_channel_configs,
- VectorChannelConfig,
VectorBusParams,
- VectorCanParams,
VectorCanFdParams,
+ VectorCanParams,
+ VectorChannelConfig,
+ get_channel_configs,
)
-from .exceptions import VectorError, VectorOperationError, VectorInitializationError
+from .exceptions import VectorError, VectorInitializationError, VectorOperationError
diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py
index d53b1418d..e60d20b0d 100644
--- a/can/interfaces/vector/canlib.py
+++ b/can/interfaces/vector/canlib.py
@@ -8,19 +8,19 @@
# ==============================
import ctypes
import logging
-import time
import os
+import time
from types import ModuleType
from typing import (
+ Any,
+ Callable,
+ Dict,
List,
NamedTuple,
Optional,
- Tuple,
Sequence,
+ Tuple,
Union,
- Any,
- Dict,
- Callable,
cast,
)
@@ -28,7 +28,7 @@
INFINITE: Optional[int]
try:
# Try builtin Python 3 Windows API
- from _winapi import WaitForSingleObject, INFINITE # type: ignore
+ from _winapi import INFINITE, WaitForSingleObject # type: ignore
HAS_EVENTS = True
except ImportError:
@@ -38,21 +38,21 @@
# Import Modules
# ==============
from can import (
- BusABC,
- Message,
- CanInterfaceNotImplementedError,
- CanInitializationError,
BitTiming,
BitTimingFd,
+ BusABC,
+ CanInitializationError,
+ CanInterfaceNotImplementedError,
+ Message,
)
+from can.typechecking import AutoDetectedConfig, CanFilters
from can.util import (
- len2dlc,
- dlc2len,
+ check_or_adjust_timing_clock,
deprecated_args_alias,
+ dlc2len,
+ len2dlc,
time_perfcounter_correlation,
- check_or_adjust_timing_clock,
)
-from can.typechecking import AutoDetectedConfig, CanFilters
# Define Module Logger
# ====================
@@ -60,8 +60,8 @@
# Import Vector API modules
# =========================
+from . import xlclass, xldefine
from .exceptions import VectorError, VectorInitializationError, VectorOperationError
-from . import xldefine, xlclass
# Import safely Vector API module for Travis tests
xldriver: Optional[ModuleType] = None
diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py
index e2fd288b9..ebc0971c1 100644
--- a/can/interfaces/vector/xldefine.py
+++ b/can/interfaces/vector/xldefine.py
@@ -6,7 +6,6 @@
# ==============================
from enum import IntEnum, IntFlag
-
MAX_MSG_LEN = 8
XL_CAN_MAX_DATA_LEN = 64
XL_INVALID_PORTHANDLE = -1
diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py
index 29791e32f..f84b3cf1d 100644
--- a/can/interfaces/vector/xldriver.py
+++ b/can/interfaces/vector/xldriver.py
@@ -10,7 +10,8 @@
import ctypes
import logging
import platform
-from .exceptions import VectorOperationError, VectorInitializationError
+
+from .exceptions import VectorInitializationError, VectorOperationError
# Define Module Logger
# ====================
diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py
index ad8774147..3eaefc230 100644
--- a/can/interfaces/virtual.py
+++ b/can/interfaces/virtual.py
@@ -6,14 +6,13 @@
and reside in the same process will receive the same messages.
"""
-from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
-
-from copy import deepcopy
import logging
-import time
import queue
-from threading import RLock
+import time
+from copy import deepcopy
from random import randint
+from threading import RLock
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
from can import CanOperationError
from can.bus import BusABC
diff --git a/can/io/__init__.py b/can/io/__init__.py
index 6dc9ac1af..12cebd52c 100644
--- a/can/io/__init__.py
+++ b/can/io/__init__.py
@@ -3,15 +3,39 @@
and Writers based off the file extension.
"""
+__all__ = [
+ "ASCReader",
+ "ASCWriter",
+ "BaseRotatingLogger",
+ "BLFReader",
+ "BLFWriter",
+ "CanutilsLogReader",
+ "CanutilsLogWriter",
+ "CSVReader",
+ "CSVWriter",
+ "Logger",
+ "LogReader",
+ "MessageSync",
+ "Printer",
+ "SizedRotatingLogger",
+ "SqliteReader",
+ "SqliteWriter",
+ "TRCFileVersion",
+ "TRCReader",
+ "TRCWriter",
+]
+
# Generic
-from .logger import Logger, BaseRotatingLogger, SizedRotatingLogger
+from .logger import BaseRotatingLogger, Logger, SizedRotatingLogger
from .player import LogReader, MessageSync
+# isort: split
+
# Format specific
-from .asc import ASCWriter, ASCReader
+from .asc import ASCReader, ASCWriter
from .blf import BLFReader, BLFWriter
from .canutils import CanutilsLogReader, CanutilsLogWriter
-from .csv import CSVWriter, CSVReader
-from .sqlite import SqliteReader, SqliteWriter
+from .csv import CSVReader, CSVWriter
from .printer import Printer
-from .trc import TRCReader, TRCWriter, TRCFileVersion
+from .sqlite import SqliteReader, SqliteWriter
+from .trc import TRCFileVersion, TRCReader, TRCWriter
diff --git a/can/io/asc.py b/can/io/asc.py
index a380f6b16..b8054ecfc 100644
--- a/can/io/asc.py
+++ b/can/io/asc.py
@@ -5,18 +5,16 @@
- https://bitbucket.org/tobylorenz/vector_asc/src/master/src/Vector/ASC/tests/unittests/data/
- under `test/data/logfile.asc`
"""
+import logging
import re
-from typing import Any, Generator, List, Optional, Dict, Union, TextIO
-
-from datetime import datetime
import time
-import logging
+from datetime import datetime
+from typing import Any, Dict, Generator, List, Optional, TextIO, Union
from ..message import Message
-from ..util import channel2int, len2dlc, dlc2len
-from .generic import FileIOMessageWriter, MessageReader
from ..typechecking import StringPathLike
-
+from ..util import channel2int, dlc2len, len2dlc
+from .generic import FileIOMessageWriter, MessageReader
CAN_MSG_EXT = 0x80000000
CAN_ID_MASK = 0x1FFFFFFF
diff --git a/can/io/blf.py b/can/io/blf.py
index 8d5ade8c8..e9dd8380f 100644
--- a/can/io/blf.py
+++ b/can/io/blf.py
@@ -12,19 +12,18 @@
objects types.
"""
-import struct
-import zlib
import datetime
-import time
import logging
-from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast, Any
+import struct
+import time
+import zlib
+from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast
from ..message import Message
-from ..util import len2dlc, dlc2len, channel2int
from ..typechecking import StringPathLike
+from ..util import channel2int, dlc2len, len2dlc
from .generic import FileIOMessageWriter, MessageReader
-
TSystemTime = Tuple[int, int, int, int, int, int, int, int]
diff --git a/can/io/canutils.py b/can/io/canutils.py
index 17d7a193f..a9dced6a1 100644
--- a/can/io/canutils.py
+++ b/can/io/canutils.py
@@ -5,11 +5,12 @@
"""
import logging
-from typing import Generator, TextIO, Union, Any
+from typing import Any, Generator, TextIO, Union
from can.message import Message
-from .generic import FileIOMessageWriter, MessageReader
+
from ..typechecking import StringPathLike
+from .generic import FileIOMessageWriter, MessageReader
log = logging.getLogger("can.io.canutils")
diff --git a/can/io/csv.py b/can/io/csv.py
index 7570d4f30..b96e69342 100644
--- a/can/io/csv.py
+++ b/can/io/csv.py
@@ -9,12 +9,13 @@
of a CSV file.
"""
-from base64 import b64encode, b64decode
-from typing import TextIO, Generator, Union, Any
+from base64 import b64decode, b64encode
+from typing import Any, Generator, TextIO, Union
from can.message import Message
-from .generic import FileIOMessageWriter, MessageReader
+
from ..typechecking import StringPathLike
+from .generic import FileIOMessageWriter, MessageReader
class CSVReader(MessageReader):
diff --git a/can/io/generic.py b/can/io/generic.py
index 77bba4501..193ec3df2 100644
--- a/can/io/generic.py
+++ b/can/io/generic.py
@@ -1,19 +1,21 @@
"""Contains generic base classes for file IO."""
import locale
from abc import ABCMeta
+from types import TracebackType
from typing import (
- Optional,
- cast,
+ Any,
+ ContextManager,
Iterable,
+ Optional,
Type,
- ContextManager,
- Any,
+ cast,
)
+
from typing_extensions import Literal
-from types import TracebackType
-import can
-import can.typechecking
+from .. import typechecking
+from ..listener import Listener
+from ..message import Message
class BaseIOHandler(ContextManager, metaclass=ABCMeta):
@@ -26,11 +28,11 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta):
was opened
"""
- file: Optional[can.typechecking.FileLike]
+ file: Optional[typechecking.FileLike]
def __init__(
self,
- file: Optional[can.typechecking.AcceptedIOType],
+ file: Optional[typechecking.AcceptedIOType],
mode: str = "rt",
**kwargs: Any,
) -> None:
@@ -42,7 +44,7 @@ def __init__(
"""
if file is None or (hasattr(file, "read") and hasattr(file, "write")):
# file is None or some file-like object
- self.file = cast(Optional[can.typechecking.FileLike], file)
+ self.file = cast(Optional[typechecking.FileLike], file)
else:
encoding: Optional[str] = (
None
@@ -52,10 +54,8 @@ def __init__(
# pylint: disable=consider-using-with
# file is some path-like object
self.file = cast(
- can.typechecking.FileLike,
- open(
- cast(can.typechecking.StringPathLike, file), mode, encoding=encoding
- ),
+ typechecking.FileLike,
+ open(cast(typechecking.StringPathLike, file), mode, encoding=encoding),
)
# for multiple inheritance
@@ -80,19 +80,19 @@ def stop(self) -> None:
self.file.close()
-class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta):
+class MessageWriter(BaseIOHandler, Listener, metaclass=ABCMeta):
"""The base class for all writers."""
- file: Optional[can.typechecking.FileLike]
+ file: Optional[typechecking.FileLike]
class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
"""A specialized base class for all writers with file descriptors."""
- file: can.typechecking.FileLike
+ file: typechecking.FileLike
def __init__(
- self, file: can.typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any
+ self, file: typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any
) -> None:
# Not possible with the type signature, but be verbose for user-friendliness
if file is None:
@@ -105,5 +105,5 @@ def file_size(self) -> int:
return self.file.tell()
-class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta):
+class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta):
"""The base class for all readers."""
diff --git a/can/io/logger.py b/can/io/logger.py
index 0477fa065..de538e866 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -2,29 +2,28 @@
See the :class:`Logger` class.
"""
+import gzip
import os
import pathlib
from abc import ABC, abstractmethod
from datetime import datetime
-import gzip
-from typing import Any, Optional, Callable, Type, Tuple, cast, Dict, Set
-
from types import TracebackType
+from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, cast
-from typing_extensions import Literal
from pkg_resources import iter_entry_points
+from typing_extensions import Literal
-from ..message import Message
from ..listener import Listener
-from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
+from ..message import Message
+from ..typechecking import AcceptedIOType, FileLike, StringPathLike
from .asc import ASCWriter
from .blf import BLFWriter
from .canutils import CanutilsLogWriter
from .csv import CSVWriter
-from .sqlite import SqliteWriter
+from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
from .printer import Printer
+from .sqlite import SqliteWriter
from .trc import TRCWriter
-from ..typechecking import StringPathLike, FileLike, AcceptedIOType
class Logger(MessageWriter):
diff --git a/can/io/player.py b/can/io/player.py
index 13a9ce60e..022ed503b 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -10,15 +10,15 @@
from pkg_resources import iter_entry_points
-from .generic import MessageReader
+from ..message import Message
+from ..typechecking import AcceptedIOType, FileLike, StringPathLike
from .asc import ASCReader
from .blf import BLFReader
from .canutils import CanutilsLogReader
from .csv import CSVReader
+from .generic import MessageReader
from .sqlite import SqliteReader
from .trc import TRCReader
-from ..typechecking import StringPathLike, FileLike, AcceptedIOType
-from ..message import Message
class LogReader(MessageReader):
diff --git a/can/io/printer.py b/can/io/printer.py
index 01da12e84..d0df71db8 100644
--- a/can/io/printer.py
+++ b/can/io/printer.py
@@ -3,12 +3,11 @@
"""
import logging
-
-from typing import Optional, TextIO, Union, Any, cast
+from typing import Any, Optional, TextIO, Union, cast
from ..message import Message
-from .generic import MessageWriter
from ..typechecking import StringPathLike
+from .generic import MessageWriter
log = logging.getLogger("can.io.printer")
diff --git a/can/io/sqlite.py b/can/io/sqlite.py
index 33f5d293f..43fd761e9 100644
--- a/can/io/sqlite.py
+++ b/can/io/sqlite.py
@@ -4,16 +4,17 @@
.. note:: The database schema is given in the documentation of the loggers.
"""
-import time
-import threading
import logging
import sqlite3
-from typing import Generator, Any
+import threading
+import time
+from typing import Any, Generator
from can.listener import BufferedReader
from can.message import Message
-from .generic import MessageWriter, MessageReader
+
from ..typechecking import StringPathLike
+from .generic import MessageReader, MessageWriter
log = logging.getLogger("can.io.sqlite")
diff --git a/can/io/trc.py b/can/io/trc.py
index d1ee2b72d..75c81b502 100644
--- a/can/io/trc.py
+++ b/can/io/trc.py
@@ -7,18 +7,17 @@
Version 1.1 will be implemented as it is most commonly used
""" # noqa
-from datetime import datetime, timedelta, timezone
-from enum import Enum
import io
-import os
import logging
-from typing import Generator, Optional, Union, TextIO, Callable, List, Dict
+import os
+from datetime import datetime, timedelta, timezone
+from enum import Enum
+from typing import Callable, Dict, Generator, List, Optional, TextIO, Union
from ..message import Message
-from ..util import channel2int, len2dlc, dlc2len
-from .generic import FileIOMessageWriter, MessageReader
from ..typechecking import StringPathLike
-
+from ..util import channel2int, dlc2len, len2dlc
+from .generic import FileIOMessageWriter, MessageReader
logger = logging.getLogger("can.io.trc")
diff --git a/can/listener.py b/can/listener.py
index e68d813d1..d6f252d17 100644
--- a/can/listener.py
+++ b/can/listener.py
@@ -2,15 +2,15 @@
This module contains the implementation of `can.Listener` and some readers.
"""
+import asyncio
import sys
import warnings
-import asyncio
from abc import ABCMeta, abstractmethod
-from queue import SimpleQueue, Empty
+from queue import Empty, SimpleQueue
from typing import Any, AsyncIterator, Optional
-from can.message import Message
from can.bus import BusABC
+from can.message import Message
class Listener(metaclass=ABCMeta):
diff --git a/can/logconvert.py b/can/logconvert.py
index 7a34deb61..49cdaf4bb 100644
--- a/can/logconvert.py
+++ b/can/logconvert.py
@@ -2,11 +2,11 @@
Convert a log file from one format to another.
"""
-import sys
import argparse
import errno
+import sys
-from can import LogReader, Logger, SizedRotatingLogger
+from can import Logger, LogReader, SizedRotatingLogger
class ArgumentParser(argparse.ArgumentParser):
diff --git a/can/logger.py b/can/logger.py
index 9448fe6b4..42312324a 100644
--- a/can/logger.py
+++ b/can/logger.py
@@ -1,14 +1,15 @@
+import argparse
+import errno
import re
import sys
-import argparse
from datetime import datetime
-import errno
-from typing import Any, Dict, List, Union, Sequence, Tuple
+from typing import Any, Dict, List, Sequence, Tuple, Union
import can
from can.io import BaseRotatingLogger
from can.io.generic import MessageWriter
from can.util import cast_from_string
+
from . import Bus, BusState, Logger, SizedRotatingLogger
from .typechecking import CanFilter, CanFilters
diff --git a/can/message.py b/can/message.py
index 48933b2da..05700ef72 100644
--- a/can/message.py
+++ b/can/message.py
@@ -6,13 +6,12 @@
starting with Python 3.7.
"""
+from copy import deepcopy
+from math import isinf, isnan
from typing import Optional
from . import typechecking
-from copy import deepcopy
-from math import isinf, isnan
-
class Message: # pylint: disable=too-many-instance-attributes; OK for a dataclass
"""
diff --git a/can/notifier.py b/can/notifier.py
index 2adae431e..fce210f49 100644
--- a/can/notifier.py
+++ b/can/notifier.py
@@ -6,7 +6,7 @@
import logging
import threading
import time
-from typing import Callable, Iterable, List, Optional, Union, Awaitable
+from typing import Awaitable, Callable, Iterable, List, Optional, Union
from can.bus import BusABC
from can.listener import Listener
diff --git a/can/player.py b/can/player.py
index fab271824..40e4cc43a 100644
--- a/can/player.py
+++ b/can/player.py
@@ -5,11 +5,11 @@
Similar to canplayer in the can-utils package.
"""
-import sys
import argparse
-from datetime import datetime
import errno
-from typing import cast, Iterable
+import sys
+from datetime import datetime
+from typing import Iterable, cast
from can import LogReader, Message, MessageSync
diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py
index 6f16b8b4d..4793ed1ff 100644
--- a/can/thread_safe_bus.py
+++ b/can/thread_safe_bus.py
@@ -12,7 +12,6 @@
from .interface import Bus
-
try:
from contextlib import nullcontext
diff --git a/can/util.py b/can/util.py
index 42d99272e..2f6fb1957 100644
--- a/can/util.py
+++ b/can/util.py
@@ -11,24 +11,24 @@
import re
import warnings
from configparser import ConfigParser
-from time import time, perf_counter, get_clock_info
+from time import get_clock_info, perf_counter, time
from typing import (
Any,
Callable,
- cast,
Dict,
Iterable,
- Tuple,
Optional,
- Union,
+ Tuple,
TypeVar,
+ Union,
+ cast,
)
import can
+
from . import typechecking
from .bit_timing import BitTiming, BitTimingFd
-from .exceptions import CanInitializationError
-from .exceptions import CanInterfaceNotImplementedError
+from .exceptions import CanInitializationError, CanInterfaceNotImplementedError
from .interfaces import VALID_INTERFACES
log = logging.getLogger("can.util")
diff --git a/can/viewer.py b/can/viewer.py
index 5539ee3fb..be7f76b73 100644
--- a/can/viewer.py
+++ b/can/viewer.py
@@ -30,20 +30,21 @@
from typing import Dict, List, Tuple, Union
from can import __version__
+
from .logger import (
- _create_bus,
- _parse_filters,
_append_filter_argument,
_create_base_argument_parser,
+ _create_bus,
_parse_additional_config,
+ _parse_filters,
)
-
logger = logging.getLogger("can.viewer")
try:
import curses
- from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE
+ from curses.ascii import ESC as KEY_ESC
+ from curses.ascii import SP as KEY_SPACE
except ImportError:
# Probably on Windows while windows-curses is not installed (e.g. in PyPy)
logger.warning(
diff --git a/doc/conf.py b/doc/conf.py
index cea93440d..aa61f243b 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -6,9 +6,9 @@
# -- Imports -------------------------------------------------------------------
-import sys
-import os
import ctypes
+import os
+import sys
from unittest.mock import MagicMock
# If extensions (or modules to document with autodoc) are in another directory,
diff --git a/pyproject.toml b/pyproject.toml
index 17d87f033..af952e51e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,3 +4,16 @@ requires = [
"wheel",
]
build-backend = "setuptools.build_meta"
+
+[tool.ruff]
+select = [
+ "F401", # unused-imports
+ "UP", # pyupgrade
+ "I", # isort
+]
+
+# Assume Python 3.7.
+target-version = "py37"
+
+[tool.ruff.isort]
+known-first-party = ["can"]
diff --git a/requirements-lint.txt b/requirements-lint.txt
index f1070e1b9..829fe5663 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1,4 +1,5 @@
pylint==2.16.4
+ruff==0.0.260
black~=23.1.0
mypy==1.0.1
mypy-extensions==0.4.3
diff --git a/setup.py b/setup.py
index 96cbc0c77..b6dfab6f2 100644
--- a/setup.py
+++ b/setup.py
@@ -5,11 +5,12 @@
Learn more at https://github.com/hardbyte/python-can/
"""
+import logging
+import re
from os import listdir
from os.path import isfile, join
-import re
-import logging
-from setuptools import setup, find_packages
+
+from setuptools import find_packages, setup
logging.basicConfig(level=logging.WARNING)
diff --git a/test/back2back_test.py b/test/back2back_test.py
index 48c98bf59..52bfaf716 100644
--- a/test/back2back_test.py
+++ b/test/back2back_test.py
@@ -4,10 +4,10 @@
This module tests two buses attached to each other.
"""
+import random
import unittest
-from time import sleep, time
from multiprocessing.dummy import Pool as ThreadPool
-import random
+from time import sleep, time
import pytest
@@ -17,12 +17,12 @@
from .config import (
IS_CI,
- IS_UNIX,
IS_OSX,
+ IS_PYPY,
IS_TRAVIS,
- TEST_INTERFACE_SOCKETCAN,
+ IS_UNIX,
TEST_CAN_FD,
- IS_PYPY,
+ TEST_INTERFACE_SOCKETCAN,
)
diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py
index 014dfb121..3adb1e7c6 100644
--- a/test/contextmanager_test.py
+++ b/test/contextmanager_test.py
@@ -5,6 +5,7 @@
"""
import unittest
+
import can
diff --git a/test/listener_test.py b/test/listener_test.py
index 9b2e9e93b..b530afa60 100644
--- a/test/listener_test.py
+++ b/test/listener_test.py
@@ -3,13 +3,13 @@
"""
"""
import asyncio
-import unittest
-import random
import logging
-import tempfile
import os
+import random
+import tempfile
+import unittest
import warnings
-from os.path import join, dirname
+from os.path import dirname, join
import can
diff --git a/test/logformats_test.py b/test/logformats_test.py
index 3486827a9..d3b0eb015 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -12,23 +12,24 @@
TODO: correctly set preserves_channel and adds_default_channel
"""
import logging
-import unittest
-from parameterized import parameterized
-import tempfile
import os
-from abc import abstractmethod, ABCMeta
-from itertools import zip_longest
+import tempfile
+import unittest
+from abc import ABCMeta, abstractmethod
from datetime import datetime
+from itertools import zip_longest
+
+from parameterized import parameterized
import can
from can.io import blf
from .data.example_data import (
+ TEST_COMMENTS,
TEST_MESSAGES_BASE,
- TEST_MESSAGES_REMOTE_FRAMES,
- TEST_MESSAGES_ERROR_FRAMES,
TEST_MESSAGES_CAN_FD,
- TEST_COMMENTS,
+ TEST_MESSAGES_ERROR_FRAMES,
+ TEST_MESSAGES_REMOTE_FRAMES,
sort_messages,
)
from .message_helper import ComparingMessagesTestCase
diff --git a/test/network_test.py b/test/network_test.py
index 58c305a38..61690c1b4 100644
--- a/test/network_test.py
+++ b/test/network_test.py
@@ -1,10 +1,10 @@
#!/usr/bin/env python
-import unittest
-import threading
-import random
import logging
+import random
+import threading
+import unittest
logging.getLogger(__file__).setLevel(logging.WARNING)
diff --git a/test/notifier_test.py b/test/notifier_test.py
index ca2093f55..6982130cf 100644
--- a/test/notifier_test.py
+++ b/test/notifier_test.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python
-import unittest
-import time
import asyncio
+import time
+import unittest
import can
diff --git a/test/serial_test.py b/test/serial_test.py
index e1df96435..d020d7232 100644
--- a/test/serial_test.py
+++ b/test/serial_test.py
@@ -12,9 +12,8 @@
import can
from can.interfaces.serial.serial_can import SerialBus
-from .message_helper import ComparingMessagesTestCase
from .config import IS_PYPY
-
+from .message_helper import ComparingMessagesTestCase
# Mentioned in #1010
TIMEOUT = 0.5 if IS_PYPY else 0.1 # 0.1 is the default set in SerialBus
diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py
index 4454cbd27..9e01be457 100644
--- a/test/simplecyclic_test.py
+++ b/test/simplecyclic_test.py
@@ -4,10 +4,10 @@
This module tests cyclic send tasks.
"""
-from time import sleep
+import gc
import unittest
+from time import sleep
from unittest.mock import MagicMock
-import gc
import can
diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py
index 30c86d6a5..f19ce95b9 100644
--- a/test/test_cyclic_socketcan.py
+++ b/test/test_cyclic_socketcan.py
@@ -3,9 +3,9 @@
"""
This module tests multiple message cyclic send tasks.
"""
+import time
import unittest
-import time
import can
from .config import TEST_INTERFACE_SOCKETCAN
diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py
index 4f3033e10..0a87f40f9 100755
--- a/test/test_interface_canalystii.py
+++ b/test/test_interface_canalystii.py
@@ -1,10 +1,14 @@
#!/usr/bin/env python
+"""
+"""
+
import unittest
-from unittest.mock import patch, call
from ctypes import c_ubyte
+from unittest.mock import call, patch
import canalystii as driver # low-level driver module, mock out this layer
+
import can
from can.interfaces.canalystii import CANalystIIBus
diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py
index 484a88b58..2ff016d97 100644
--- a/test/test_interface_ixxat.py
+++ b/test/test_interface_ixxat.py
@@ -8,6 +8,7 @@
"""
import unittest
+
import can
diff --git a/test/test_interface_ixxat_fd.py b/test/test_interface_ixxat_fd.py
index 80060a7ed..7274498aa 100644
--- a/test/test_interface_ixxat_fd.py
+++ b/test/test_interface_ixxat_fd.py
@@ -8,6 +8,7 @@
"""
import unittest
+
import can
diff --git a/test/test_kvaser.py b/test/test_kvaser.py
index fda8b8316..6e7ccea38 100644
--- a/test/test_kvaser.py
+++ b/test/test_kvaser.py
@@ -10,8 +10,7 @@
import pytest
import can
-from can.interfaces.kvaser import canlib
-from can.interfaces.kvaser import constants
+from can.interfaces.kvaser import canlib, constants
class KvaserTest(unittest.TestCase):
diff --git a/test/test_logger.py b/test/test_logger.py
index bb0015a89..083e4d19c 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -4,12 +4,12 @@
This module tests the functions inside of logger.py
"""
-import unittest
-from unittest import mock
-from unittest.mock import Mock
import gzip
import os
import sys
+import unittest
+from unittest import mock
+from unittest.mock import Mock
import pytest
diff --git a/test/test_message_class.py b/test/test_message_class.py
index 4840402ff..8e2367034 100644
--- a/test/test_message_class.py
+++ b/test/test_message_class.py
@@ -1,22 +1,21 @@
#!/usr/bin/env python
-import unittest
+import pickle
import sys
-from math import isinf, isnan
+import unittest
from copy import copy, deepcopy
-import pickle
from datetime import timedelta
+from math import isinf, isnan
-from hypothesis import HealthCheck, given, settings
import hypothesis.errors
import hypothesis.strategies as st
+import pytest
+from hypothesis import HealthCheck, given, settings
from can import Message
+from .config import IS_GITHUB_ACTIONS, IS_PYPY, IS_WINDOWS
from .message_helper import ComparingMessagesTestCase
-from .config import IS_GITHUB_ACTIONS, IS_WINDOWS, IS_PYPY
-
-import pytest
class TestMessageClass(unittest.TestCase):
diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py
index e6fe16d46..a73e07aa2 100644
--- a/test/test_message_filtering.py
+++ b/test/test_message_filtering.py
@@ -10,7 +10,6 @@
from .data.example_data import TEST_ALL_MESSAGES
-
EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True)
HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True)
diff --git a/test/test_message_sync.py b/test/test_message_sync.py
index 7552915e7..90cbe372c 100644
--- a/test/test_message_sync.py
+++ b/test/test_message_sync.py
@@ -4,19 +4,18 @@
This module tests :class:`can.MessageSync`.
"""
-from copy import copy
-import time
import gc
-
+import time
import unittest
+from copy import copy
+
import pytest
-from can import MessageSync, Message
+from can import Message, MessageSync
-from .config import IS_CI, IS_TRAVIS, IS_OSX, IS_GITHUB_ACTIONS, IS_LINUX
-from .message_helper import ComparingMessagesTestCase
+from .config import IS_CI, IS_GITHUB_ACTIONS, IS_LINUX, IS_OSX, IS_TRAVIS
from .data.example_data import TEST_MESSAGES_BASE
-
+from .message_helper import ComparingMessagesTestCase
TEST_FEWER_MESSAGES = TEST_MESSAGES_BASE[::2]
diff --git a/test/test_neousys.py b/test/test_neousys.py
index 26a220048..3acbf6389 100644
--- a/test/test_neousys.py
+++ b/test/test_neousys.py
@@ -1,20 +1,14 @@
#!/usr/bin/env python
-import ctypes
-import os
-import pickle
import unittest
-from unittest.mock import Mock
-
from ctypes import (
+ POINTER,
byref,
+ c_ubyte,
cast,
- POINTER,
sizeof,
- c_ubyte,
)
-
-import pytest
+from unittest.mock import Mock
import can
from can.interfaces.neousys import neousys
diff --git a/test/test_neovi.py b/test/test_neovi.py
index 181f92377..d8f54960a 100644
--- a/test/test_neovi.py
+++ b/test/test_neovi.py
@@ -4,6 +4,7 @@
"""
import pickle
import unittest
+
from can.interfaces.ics_neovi import ICSApiError
diff --git a/test/test_player.py b/test/test_player.py
index 9bdd484b8..5ad6e774c 100755
--- a/test/test_player.py
+++ b/test/test_player.py
@@ -4,12 +4,13 @@
This module tests the functions inside of player.py
"""
+import io
+import os
+import sys
import unittest
from unittest import mock
from unittest.mock import Mock
-import os
-import sys
-import io
+
import can
import can.player
diff --git a/test/test_robotell.py b/test/test_robotell.py
index 64f4acaf1..c0658ef2c 100644
--- a/test/test_robotell.py
+++ b/test/test_robotell.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
import unittest
+
import can
diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py
index ad4388bf7..8230168b9 100644
--- a/test/test_rotating_loggers.py
+++ b/test/test_rotating_loggers.py
@@ -9,6 +9,7 @@
from unittest.mock import Mock
import can
+
from .data.example_data import generate_message
diff --git a/test/test_scripts.py b/test/test_scripts.py
index a22820bd8..e7bd7fd09 100644
--- a/test/test_scripts.py
+++ b/test/test_scripts.py
@@ -4,10 +4,10 @@
This module tests that the scripts are all callable.
"""
+import errno
import subprocess
-import unittest
import sys
-import errno
+import unittest
from abc import ABCMeta, abstractmethod
from .config import *
diff --git a/test/test_slcan.py b/test/test_slcan.py
index 8db2d402a..774a2dec5 100644
--- a/test/test_slcan.py
+++ b/test/test_slcan.py
@@ -1,9 +1,10 @@
#!/usr/bin/env python
import unittest
+
import can
-from .config import IS_PYPY
+from .config import IS_PYPY
"""
Mentioned in #1010 & #1490
diff --git a/test/test_socketcan.py b/test/test_socketcan.py
index 324890dad..90a143a36 100644
--- a/test/test_socketcan.py
+++ b/test/test_socketcan.py
@@ -8,8 +8,8 @@
import unittest
import warnings
from unittest.mock import patch
-import can
+import can
from can.interfaces.socketcan.constants import (
CAN_BCM_TX_DELETE,
CAN_BCM_TX_SETUP,
@@ -18,13 +18,14 @@
TX_COUNTEVT,
)
from can.interfaces.socketcan.socketcan import (
+ BcmMsgHead,
bcm_header_factory,
build_bcm_header,
- build_bcm_tx_delete_header,
build_bcm_transmit_header,
+ build_bcm_tx_delete_header,
build_bcm_update_header,
- BcmMsgHead,
)
+
from .config import IS_LINUX, IS_PYPY
diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py
index 29ceb11c0..0f4e1b4ea 100644
--- a/test/test_socketcan_helpers.py
+++ b/test/test_socketcan_helpers.py
@@ -5,13 +5,11 @@
"""
import gzip
-from base64 import b64decode
import unittest
+from base64 import b64decode
from unittest import mock
-from subprocess import CalledProcessError
-
-from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str
+from can.interfaces.socketcan.utils import error_code_to_str, find_available_interfaces
from .config import IS_LINUX, TEST_INTERFACE_SOCKETCAN
diff --git a/test/test_util.py b/test/test_util.py
index e77401688..a4aacdf86 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -10,10 +10,10 @@
from can.util import (
_create_bus_config,
_rename_kwargs,
+ cast_from_string,
channel2int,
- deprecated_args_alias,
check_or_adjust_timing_clock,
- cast_from_string,
+ deprecated_args_alias,
)
diff --git a/test/test_vector.py b/test/test_vector.py
index 7694b31aa..b6a0632a8 100644
--- a/test/test_vector.py
+++ b/test/test_vector.py
@@ -9,22 +9,24 @@
import pickle
import sys
import time
+from test.config import IS_WINDOWS
from unittest.mock import Mock
import pytest
import can
from can.interfaces.vector import (
- canlib,
- xldefine,
- xlclass,
+ VectorBusParams,
+ VectorCanFdParams,
+ VectorCanParams,
+ VectorChannelConfig,
VectorError,
VectorInitializationError,
VectorOperationError,
- VectorChannelConfig,
+ canlib,
+ xlclass,
+ xldefine,
)
-from can.interfaces.vector import VectorBusParams, VectorCanParams, VectorCanFdParams
-from test.config import IS_WINDOWS
XLDRIVER_FOUND = canlib.xldriver is not None
diff --git a/test/test_viewer.py b/test/test_viewer.py
index baef10bda..ecc594915 100644
--- a/test/test_viewer.py
+++ b/test/test_viewer.py
@@ -30,14 +30,13 @@
import time
import unittest
from collections import defaultdict
-from typing import Dict, Tuple, Union
+from test.config import IS_CI
from unittest.mock import patch
import pytest
import can
from can.viewer import CanViewer, parse_args
-from test.config import IS_CI
# Allow the curses module to be missing (e.g. on PyPy on Windows)
try:
diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py
index cd5e7895e..4e7596caf 100644
--- a/test/zero_dlc_test.py
+++ b/test/zero_dlc_test.py
@@ -3,9 +3,8 @@
"""
"""
-from time import sleep
-import unittest
import logging
+import unittest
import can
From ccfd7f30ec538f1fbaa676dd92e62c9b90ec0203 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 3 Apr 2023 13:10:46 +0200
Subject: [PATCH 246/475] Test python 3.12 alpha (#1554)
* test 3.12
* fix AttributeError on 3.12
---
.github/workflows/ci.yml | 5 +++++
test/test_neousys.py | 16 ++++++++--------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 465d5959b..4d0997473 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,6 +27,11 @@ jobs:
"pypy-3.8",
"pypy-3.9",
]
+ include:
+ # Only test on a single configuration while there are just pre-releases
+ - os: ubuntu-latest
+ experimental: true
+ python-version: "3.12.0-alpha - 3.12.0"
fail-fast: false
steps:
- uses: actions/checkout@v3
diff --git a/test/test_neousys.py b/test/test_neousys.py
index 3acbf6389..080278d13 100644
--- a/test/test_neousys.py
+++ b/test/test_neousys.py
@@ -36,12 +36,12 @@ def tearDown(self) -> None:
def test_bus_creation(self) -> None:
self.assertIsInstance(self.bus, neousys.NeousysBus)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Setup.called)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Start.called)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.called)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterStatus.called)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.not_called)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.not_called)
+ neousys.NEOUSYS_CANLIB.CAN_Setup.assert_called()
+ neousys.NEOUSYS_CANLIB.CAN_Start.assert_called()
+ neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.assert_called()
+ neousys.NEOUSYS_CANLIB.CAN_RegisterStatus.assert_called()
+ neousys.NEOUSYS_CANLIB.CAN_Send.assert_not_called()
+ neousys.NEOUSYS_CANLIB.CAN_Stop.assert_not_called()
CAN_Start_args = (
can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0]
@@ -95,11 +95,11 @@ def test_send(self) -> None:
arbitration_id=0x01, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=False
)
self.bus.send(msg)
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.called)
+ neousys.NEOUSYS_CANLIB.CAN_Send.assert_called()
def test_shutdown(self) -> None:
self.bus.shutdown()
- self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.called)
+ neousys.NEOUSYS_CANLIB.CAN_Stop.assert_called()
if __name__ == "__main__":
From d7d617b7700ef6940e48576bf91b1850a0a4ff0d Mon Sep 17 00:00:00 2001
From: Nick James Kirkby <20824939+driftregion@users.noreply.github.com>
Date: Mon, 3 Apr 2023 21:20:07 +0800
Subject: [PATCH 247/475] Add MF4 support (#1289)
* add extra dependency
* add mf4 io writer
* update asammdf requirement
* changes after initial review
* simplify append call
* add MF4Reader class
* start testing
* passes tests
* update documentation
* update docs and CI scripts
* retrigger build
* update setup.py according to review
* remove debug save file
* changes after review
* updates after review
* add cython requirement
* fixes after review:
* fix documentation
* fix item access on FD_DLC2LEN dict
* add compression argument to MF4Writer stop method
* add MF4Writer and MF4Reader to logger and player modules
* reformat and change setup.py accordingly
* cleanup test file
* cleanup docs
* cleanups and fix linter problems
* re-add change to can.io.Logger; it somehow went lost while rebasing
* remove leftover __future__ import
* fix typing error in can.io's player.py and logger.py
* remove diff noise, remove deprecated appveyor CI
* run black
* Fix import errors
* add acquisition source needed by asammdf to extract bus logging
* use correct source type, add md5 digest required by asammdf
* refactor test_extension_matching tests to use explicit extensions
* satiate mypy
* update mf4 implementation
* fix AttributeError
* format black
* refactoring
* fix bugs and add direction support
* add timestamps to avoid ambiguity
* set allowed_timestamp_delta to 1e-4
* read into BytesIO
* Add restriction to docstring
* implement file_size
* constrain mf4 dependencies to pass CI tests
* format docstring
* add mf4 extra
* Update doc/listeners.rst
* Remove platform specifiers for asammdf
* Format code with black
* remove unnecessary parenthesis
* install asammdf only for CPython <= 3.12
* fix NameError
---------
Co-authored-by: danielhrisca
Co-authored-by: Felix Divo
Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Co-authored-by: Brian Thorne
Co-authored-by: Brian Thorne
Co-authored-by: hardbyte
---
.github/workflows/ci.yml | 2 +-
.readthedocs.yml | 1 +
README.rst | 2 +-
can/__init__.py | 4 +
can/io/__init__.py | 3 +
can/io/logger.py | 18 +-
can/io/mf4.py | 480 ++++++++++++++++++++++++++++++++++++++
can/io/player.py | 25 +-
doc/listeners.rst | 31 +++
setup.cfg | 2 +-
setup.py | 1 +
test/data/example_data.py | 35 ++-
test/logformats_test.py | 93 +++++---
tox.ini | 1 +
14 files changed, 649 insertions(+), 49 deletions(-)
create mode 100644 can/io/mf4.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4d0997473..949df6aab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -141,7 +141,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -e .[canalystii,gs_usb]
+ pip install -e .[canalystii,gs_usb,mf4]
pip install -r doc/doc-requirements.txt
- name: Build documentation
run: |
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 74cb9dbdd..32be9c7b5 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -29,3 +29,4 @@ python:
extra_requirements:
- canalystii
- gs_usb
+ - mf4
diff --git a/README.rst b/README.rst
index 6e65e505c..3c951e866 100644
--- a/README.rst
+++ b/README.rst
@@ -78,7 +78,7 @@ Features
- receiving, sending, and periodically sending messages
- normal and extended arbitration IDs
- `CAN FD `__ support
-- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), TRC, CSV, SQLite, and Canutils log
+- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), MF4 (Measurement Data Format v4 by ASAM), TRC, CSV, SQLite, and Canutils log
- efficient in-kernel or in-hardware filtering of messages on supported interfaces
- bus configuration reading from a file or from environment variables
- command line tools for working with CAN buses (see the `docs `__)
diff --git a/can/__init__.py b/can/__init__.py
index 5cc054c19..28416fb61 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -41,6 +41,8 @@
"ModifiableCyclicTaskABC",
"Message",
"MessageSync",
+ "MF4Reader",
+ "MF4Writer",
"Notifier",
"Printer",
"RedirectReader",
@@ -94,6 +96,8 @@
Logger,
LogReader,
MessageSync,
+ MF4Reader,
+ MF4Writer,
Printer,
SizedRotatingLogger,
SqliteReader,
diff --git a/can/io/__init__.py b/can/io/__init__.py
index 12cebd52c..05b8619f2 100644
--- a/can/io/__init__.py
+++ b/can/io/__init__.py
@@ -16,6 +16,8 @@
"Logger",
"LogReader",
"MessageSync",
+ "MF4Reader",
+ "MF4Writer",
"Printer",
"SizedRotatingLogger",
"SqliteReader",
@@ -36,6 +38,7 @@
from .blf import BLFReader, BLFWriter
from .canutils import CanutilsLogReader, CanutilsLogWriter
from .csv import CSVReader, CSVWriter
+from .mf4 import MF4Reader, MF4Writer
from .printer import Printer
from .sqlite import SqliteReader, SqliteWriter
from .trc import TRCFileVersion, TRCReader, TRCWriter
diff --git a/can/io/logger.py b/can/io/logger.py
index de538e866..07d288ba3 100644
--- a/can/io/logger.py
+++ b/can/io/logger.py
@@ -21,6 +21,7 @@
from .canutils import CanutilsLogWriter
from .csv import CSVWriter
from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
+from .mf4 import MF4Writer
from .printer import Printer
from .sqlite import SqliteWriter
from .trc import TRCWriter
@@ -38,6 +39,7 @@ class Logger(MessageWriter):
* .log :class:`can.CanutilsLogWriter`
* .trc :class:`can.TRCWriter`
* .txt :class:`can.Printer`
+ * .mf4 :class:`can.MF4Writer` (optional, depends on asammdf)
Any of these formats can be used with gzip compression by appending
the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not
@@ -59,6 +61,7 @@ class Logger(MessageWriter):
".csv": CSVWriter,
".db": SqliteWriter,
".log": CanutilsLogWriter,
+ ".mf4": MF4Writer,
".trc": TRCWriter,
".txt": Printer,
}
@@ -68,10 +71,12 @@ def __new__( # type: ignore
cls: Any, filename: Optional[StringPathLike], **kwargs: Any
) -> MessageWriter:
"""
- :param filename: the filename/path of the file to write to,
- may be a path-like object or None to
- instantiate a :class:`~can.Printer`
- :raises ValueError: if the filename's suffix is of an unknown file type
+ :param filename:
+ the filename/path of the file to write to,
+ may be a path-like object or None to
+ instantiate a :class:`~can.Printer`
+ :raises ValueError:
+ if the filename's suffix is of an unknown file type
"""
if filename is None:
return Printer(**kwargs)
@@ -92,7 +97,10 @@ def __new__( # type: ignore
suffix, file_or_filename = Logger.compress(filename, **kwargs)
try:
- return Logger.message_writers[suffix](file=file_or_filename, **kwargs)
+ LoggerType = Logger.message_writers[suffix]
+ if LoggerType is None:
+ raise ValueError(f'failed to import logger for extension "{suffix}"')
+ return LoggerType(file=file_or_filename, **kwargs)
except KeyError:
raise ValueError(
f'No write support for this unknown log format "{suffix}"'
diff --git a/can/io/mf4.py b/can/io/mf4.py
new file mode 100644
index 000000000..faad9c37a
--- /dev/null
+++ b/can/io/mf4.py
@@ -0,0 +1,480 @@
+"""
+Contains handling of MF4 logging files.
+
+MF4 files represent Measurement Data Format (MDF) version 4 as specified by
+the ASAM MDF standard (see https://www.asam.net/standards/detail/mdf/)
+"""
+import logging
+from datetime import datetime
+from hashlib import md5
+from io import BufferedIOBase, BytesIO
+from pathlib import Path
+from typing import Any, BinaryIO, Generator, Optional, Union, cast
+
+from ..message import Message
+from ..typechecking import StringPathLike
+from ..util import channel2int, dlc2len, len2dlc
+from .generic import FileIOMessageWriter, MessageReader
+
+logger = logging.getLogger("can.io.mf4")
+
+try:
+ import asammdf
+ import numpy as np
+ from asammdf import Signal
+ from asammdf.blocks.mdf_v4 import MDF4
+ from asammdf.blocks.v4_blocks import SourceInformation
+ from asammdf.blocks.v4_constants import BUS_TYPE_CAN, SOURCE_BUS
+ from asammdf.mdf import MDF
+
+ STD_DTYPE = np.dtype(
+ [
+ ("CAN_DataFrame.BusChannel", " None:
+ """
+ :param file:
+ A path-like object or as file-like object to write to.
+ If this is a file-like object, is has to be opened in
+ binary write mode, not text write mode.
+ :param database:
+ optional path to a DBC or ARXML file that contains message description.
+ :param compression_level:
+ compression option as integer (default 2)
+ * 0 - no compression
+ * 1 - deflate (slower, but produces smaller files)
+ * 2 - transposition + deflate (slowest, but produces the smallest files)
+ """
+ if asammdf is None:
+ raise NotImplementedError(
+ "The asammdf package was not found. Install python-can with "
+ "the optional dependency [mf4] to use the MF4Writer."
+ )
+
+ if kwargs.get("append", False):
+ raise ValueError(
+ f"{self.__class__.__name__} is currently not equipped to "
+ f"append messages to an existing file."
+ )
+
+ super().__init__(file, mode="w+b")
+ now = datetime.now()
+ self._mdf = cast(MDF4, MDF(version="4.10"))
+ self._mdf.header.start_time = now
+ self.last_timestamp = self._start_time = now.timestamp()
+
+ self._compression_level = compression_level
+
+ if database:
+ database = Path(database).resolve()
+ if database.exists():
+ data = database.read_bytes()
+ attachment = data, database.name, md5(data).digest()
+ else:
+ attachment = None
+ else:
+ attachment = None
+
+ acquisition_source = SourceInformation(
+ source_type=SOURCE_BUS, bus_type=BUS_TYPE_CAN
+ )
+
+ # standard frames group
+ self._mdf.append(
+ Signal(
+ name="CAN_DataFrame",
+ samples=np.array([], dtype=STD_DTYPE),
+ timestamps=np.array([], dtype=" int:
+ """Return an estimate of the current file size in bytes."""
+ # TODO: find solution without accessing private attributes of asammdf
+ return cast(int, self._mdf._tempfile.tell()) # pylint: disable=protected-access
+
+ def stop(self) -> None:
+ self._mdf.save(self.file, compression=self._compression_level)
+ self._mdf.close()
+ super().stop()
+
+ def on_message_received(self, msg: Message) -> None:
+ channel = channel2int(msg.channel)
+
+ timestamp = msg.timestamp
+ if timestamp is None:
+ timestamp = self.last_timestamp
+ else:
+ self.last_timestamp = max(self.last_timestamp, timestamp)
+
+ timestamp -= self._start_time
+
+ if msg.is_remote_frame:
+ if channel is not None:
+ self._rtr_buffer["CAN_RemoteFrame.BusChannel"] = channel
+
+ self._rtr_buffer["CAN_RemoteFrame.ID"] = msg.arbitration_id
+ self._rtr_buffer["CAN_RemoteFrame.IDE"] = int(msg.is_extended_id)
+ self._rtr_buffer["CAN_RemoteFrame.Dir"] = 0 if msg.is_rx else 1
+ self._rtr_buffer["CAN_RemoteFrame.DLC"] = msg.dlc
+
+ sigs = [(np.array([timestamp]), None), (self._rtr_buffer, None)]
+ self._mdf.extend(2, sigs)
+
+ elif msg.is_error_frame:
+ if channel is not None:
+ self._err_buffer["CAN_ErrorFrame.BusChannel"] = channel
+
+ self._err_buffer["CAN_ErrorFrame.ID"] = msg.arbitration_id
+ self._err_buffer["CAN_ErrorFrame.IDE"] = int(msg.is_extended_id)
+ self._err_buffer["CAN_ErrorFrame.Dir"] = 0 if msg.is_rx else 1
+ data = msg.data
+ size = len(data)
+ self._err_buffer["CAN_ErrorFrame.DataLength"] = size
+ self._err_buffer["CAN_ErrorFrame.DataBytes"][0, :size] = data
+ if msg.is_fd:
+ self._err_buffer["CAN_ErrorFrame.DLC"] = len2dlc(msg.dlc)
+ self._err_buffer["CAN_ErrorFrame.ESI"] = int(msg.error_state_indicator)
+ self._err_buffer["CAN_ErrorFrame.BRS"] = int(msg.bitrate_switch)
+ self._err_buffer["CAN_ErrorFrame.EDL"] = 1
+ else:
+ self._err_buffer["CAN_ErrorFrame.DLC"] = msg.dlc
+ self._err_buffer["CAN_ErrorFrame.ESI"] = 0
+ self._err_buffer["CAN_ErrorFrame.BRS"] = 0
+ self._err_buffer["CAN_ErrorFrame.EDL"] = 0
+
+ sigs = [(np.array([timestamp]), None), (self._err_buffer, None)]
+ self._mdf.extend(1, sigs)
+
+ else:
+ if channel is not None:
+ self._std_buffer["CAN_DataFrame.BusChannel"] = channel
+
+ self._std_buffer["CAN_DataFrame.ID"] = msg.arbitration_id
+ self._std_buffer["CAN_DataFrame.IDE"] = int(msg.is_extended_id)
+ self._std_buffer["CAN_DataFrame.Dir"] = 0 if msg.is_rx else 1
+ data = msg.data
+ size = len(data)
+ self._std_buffer["CAN_DataFrame.DataLength"] = size
+ self._std_buffer["CAN_DataFrame.DataBytes"][0, :size] = data
+ if msg.is_fd:
+ self._std_buffer["CAN_DataFrame.DLC"] = len2dlc(msg.dlc)
+ self._std_buffer["CAN_DataFrame.ESI"] = int(msg.error_state_indicator)
+ self._std_buffer["CAN_DataFrame.BRS"] = int(msg.bitrate_switch)
+ self._std_buffer["CAN_DataFrame.EDL"] = 1
+ else:
+ self._std_buffer["CAN_DataFrame.DLC"] = msg.dlc
+ self._std_buffer["CAN_DataFrame.ESI"] = 0
+ self._std_buffer["CAN_DataFrame.BRS"] = 0
+ self._std_buffer["CAN_DataFrame.EDL"] = 0
+
+ sigs = [(np.array([timestamp]), None), (self._std_buffer, None)]
+ self._mdf.extend(0, sigs)
+
+ # reset buffer structure
+ self._std_buffer = np.zeros(1, dtype=STD_DTYPE)
+ self._err_buffer = np.zeros(1, dtype=ERR_DTYPE)
+ self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE)
+
+
+class MF4Reader(MessageReader):
+ """
+ Iterator of CAN messages from a MF4 logging file.
+
+ The MF4Reader only supports MF4 files that were recorded with python-can.
+ """
+
+ def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
+ """
+ :param file: a path-like object or as file-like object to read from
+ If this is a file-like object, is has to be opened in
+ binary read mode, not text read mode.
+ """
+ if asammdf is None:
+ raise NotImplementedError(
+ "The asammdf package was not found. Install python-can with "
+ "the optional dependency [mf4] to use the MF4Reader."
+ )
+
+ super().__init__(file, mode="rb")
+
+ self._mdf: MDF4
+ if isinstance(file, BufferedIOBase):
+ self._mdf = MDF(BytesIO(file.read()))
+ else:
+ self._mdf = MDF(file)
+
+ self.start_timestamp = self._mdf.header.start_time.timestamp()
+
+ masters = [self._mdf.get_master(i) for i in range(3)]
+
+ masters = [
+ np.core.records.fromarrays((master, np.ones(len(master)) * i))
+ for i, master in enumerate(masters)
+ ]
+
+ self.masters = np.sort(np.concatenate(masters))
+
+ def __iter__(self) -> Generator[Message, None, None]:
+ standard_counter = 0
+ error_counter = 0
+ rtr_counter = 0
+
+ for timestamp, group_index in self.masters:
+ # standard frames
+ if group_index == 0:
+ sample = self._mdf.get(
+ "CAN_DataFrame",
+ group=group_index,
+ raw=True,
+ record_offset=standard_counter,
+ record_count=1,
+ )
+
+ try:
+ channel = int(sample["CAN_DataFrame.BusChannel"][0])
+ except ValueError:
+ channel = None
+
+ if sample["CAN_DataFrame.EDL"] == 0:
+ is_extended_id = bool(sample["CAN_DataFrame.IDE"][0])
+ arbitration_id = int(sample["CAN_DataFrame.ID"][0])
+ is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0
+ size = int(sample["CAN_DataFrame.DataLength"][0])
+ dlc = int(sample["CAN_DataFrame.DLC"][0])
+ data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes()
+
+ msg = Message(
+ timestamp=timestamp + self.start_timestamp,
+ is_error_frame=False,
+ is_remote_frame=False,
+ is_fd=False,
+ is_extended_id=is_extended_id,
+ channel=channel,
+ is_rx=is_rx,
+ arbitration_id=arbitration_id,
+ data=data,
+ dlc=dlc,
+ )
+
+ else:
+ is_extended_id = bool(sample["CAN_DataFrame.IDE"][0])
+ arbitration_id = int(sample["CAN_DataFrame.ID"][0])
+ is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0
+ size = int(sample["CAN_DataFrame.DataLength"][0])
+ dlc = dlc2len(sample["CAN_DataFrame.DLC"][0])
+ data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes()
+ error_state_indicator = bool(sample["CAN_DataFrame.ESI"][0])
+ bitrate_switch = bool(sample["CAN_DataFrame.BRS"][0])
+
+ msg = Message(
+ timestamp=timestamp + self.start_timestamp,
+ is_error_frame=False,
+ is_remote_frame=False,
+ is_fd=True,
+ is_extended_id=is_extended_id,
+ channel=channel,
+ arbitration_id=arbitration_id,
+ is_rx=is_rx,
+ data=data,
+ dlc=dlc,
+ bitrate_switch=bitrate_switch,
+ error_state_indicator=error_state_indicator,
+ )
+
+ yield msg
+ standard_counter += 1
+
+ # error frames
+ elif group_index == 1:
+ sample = self._mdf.get(
+ "CAN_ErrorFrame",
+ group=group_index,
+ raw=True,
+ record_offset=error_counter,
+ record_count=1,
+ )
+
+ try:
+ channel = int(sample["CAN_ErrorFrame.BusChannel"][0])
+ except ValueError:
+ channel = None
+
+ if sample["CAN_ErrorFrame.EDL"] == 0:
+ is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0])
+ arbitration_id = int(sample["CAN_ErrorFrame.ID"][0])
+ is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0
+ size = int(sample["CAN_ErrorFrame.DataLength"][0])
+ dlc = int(sample["CAN_ErrorFrame.DLC"][0])
+ data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes()
+
+ msg = Message(
+ timestamp=timestamp + self.start_timestamp,
+ is_error_frame=True,
+ is_remote_frame=False,
+ is_fd=False,
+ is_extended_id=is_extended_id,
+ channel=channel,
+ arbitration_id=arbitration_id,
+ is_rx=is_rx,
+ data=data,
+ dlc=dlc,
+ )
+
+ else:
+ is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0])
+ arbitration_id = int(sample["CAN_ErrorFrame.ID"][0])
+ is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0
+ size = int(sample["CAN_ErrorFrame.DataLength"][0])
+ dlc = dlc2len(sample["CAN_ErrorFrame.DLC"][0])
+ data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes()
+ error_state_indicator = bool(sample["CAN_ErrorFrame.ESI"][0])
+ bitrate_switch = bool(sample["CAN_ErrorFrame.BRS"][0])
+
+ msg = Message(
+ timestamp=timestamp + self.start_timestamp,
+ is_error_frame=True,
+ is_remote_frame=False,
+ is_fd=True,
+ is_extended_id=is_extended_id,
+ channel=channel,
+ arbitration_id=arbitration_id,
+ is_rx=is_rx,
+ data=data,
+ dlc=dlc,
+ bitrate_switch=bitrate_switch,
+ error_state_indicator=error_state_indicator,
+ )
+
+ yield msg
+ error_counter += 1
+
+ # remote frames
+ else:
+ sample = self._mdf.get(
+ "CAN_RemoteFrame",
+ group=group_index,
+ raw=True,
+ record_offset=rtr_counter,
+ record_count=1,
+ )
+
+ try:
+ channel = int(sample["CAN_RemoteFrame.BusChannel"][0])
+ except ValueError:
+ channel = None
+
+ is_extended_id = bool(sample["CAN_RemoteFrame.IDE"][0])
+ arbitration_id = int(sample["CAN_RemoteFrame.ID"][0])
+ is_rx = int(sample["CAN_RemoteFrame.Dir"][0]) == 0
+ dlc = int(sample["CAN_RemoteFrame.DLC"][0])
+
+ msg = Message(
+ timestamp=timestamp + self.start_timestamp,
+ is_error_frame=False,
+ is_remote_frame=True,
+ is_fd=False,
+ is_extended_id=is_extended_id,
+ channel=channel,
+ arbitration_id=arbitration_id,
+ is_rx=is_rx,
+ dlc=dlc,
+ )
+
+ yield msg
+
+ rtr_counter += 1
+
+ self.stop()
+
+ def stop(self) -> None:
+ self._mdf.close()
+ super().stop()
diff --git a/can/io/player.py b/can/io/player.py
index 022ed503b..98a556b62 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -1,13 +1,14 @@
"""
This module contains the generic :class:`LogReader` as
well as :class:`MessageSync` which plays back messages
-in the recorded order an time intervals.
+in the recorded order and time intervals.
"""
import gzip
import pathlib
import time
import typing
+import typing_extensions
from pkg_resources import iter_entry_points
from ..message import Message
@@ -20,6 +21,19 @@
from .sqlite import SqliteReader
from .trc import TRCReader
+MF4Reader: typing.Optional[typing.Type[MessageReader]]
+try:
+ from .mf4 import MF4Reader
+except ImportError:
+ MF4Reader = None
+
+
+_OPTIONAL_READERS: typing_extensions.Final[
+ typing.Dict[str, typing.Type[MessageReader]]
+] = {}
+if MF4Reader:
+ _OPTIONAL_READERS[".mf4"] = MF4Reader
+
class LogReader(MessageReader):
"""
@@ -31,6 +45,7 @@ class LogReader(MessageReader):
* .csv
* .db
* .log
+ * .mf4 (optional, depends on asammdf)
* .trc
Gzip compressed files can be used as long as the original
@@ -52,13 +67,14 @@ class LogReader(MessageReader):
"""
fetched_plugins = False
- message_readers: typing.Dict[str, typing.Type[MessageReader]] = {
+ message_readers: typing.Dict[str, typing.Optional[typing.Type[MessageReader]]] = {
".asc": ASCReader,
".blf": BLFReader,
".csv": CSVReader,
".db": SqliteReader,
".log": CanutilsLogReader,
".trc": TRCReader,
+ **_OPTIONAL_READERS,
}
@staticmethod
@@ -86,11 +102,14 @@ def __new__( # type: ignore
if suffix == ".gz":
suffix, file_or_filename = LogReader.decompress(filename)
try:
- return LogReader.message_readers[suffix](file=file_or_filename, **kwargs)
+ ReaderType = LogReader.message_readers[suffix]
except KeyError:
raise ValueError(
f'No read support for this unknown log format "{suffix}"'
) from None
+ if ReaderType is None:
+ raise ImportError(f"failed to import reader for extension {suffix}")
+ return ReaderType(file=file_or_filename, **kwargs)
@staticmethod
def decompress(
diff --git a/doc/listeners.rst b/doc/listeners.rst
index 260854d2a..110e960d3 100644
--- a/doc/listeners.rst
+++ b/doc/listeners.rst
@@ -211,6 +211,37 @@ The following class can be used to read messages from BLF file:
.. autoclass:: can.BLFReader
:members:
+
+MF4 (Measurement Data Format v4)
+--------------------------------
+
+Implements support for MF4 (Measurement Data Format v4) which is a proprietary
+format from ASAM (Association for Standardization of Automation and Measuring Systems), widely used in
+many automotive software (Vector CANape, ETAS INCA, dSPACE ControlDesk, etc.).
+
+The data is stored in a compressed format which makes it compact.
+
+.. note:: MF4 support has to be installed as an extra with for example ``pip install python-can[mf4]``.
+
+.. note:: Channels will be converted to integers.
+
+.. note:: MF4Writer does not suppport the append mode.
+
+
+.. autoclass:: can.MF4Writer
+ :members:
+
+The MDF format is very flexible regarding the internal structure and it is used to handle data from multiple sources, not just CAN bus logging.
+MDF4Writer will always create a fixed internal file structure where there will be three channel groups (for standard, error and remote frames).
+Using this fixed file structure allows for a simple implementation of MDF4Writer and MF4Reader classes.
+Therefor MF4Reader can only replay files created with MF4Writer.
+
+The following class can be used to read messages from MF4 file:
+
+.. autoclass:: can.MF4Reader
+ :members:
+
+
TRC
----
diff --git a/setup.cfg b/setup.cfg
index 2f3ee032f..3e121b650 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,7 +8,7 @@ ignore_missing_imports = True
no_implicit_optional = True
disallow_incomplete_defs = True
warn_redundant_casts = True
-warn_unused_ignores = True
+warn_unused_ignores = False
exclude =
(?x)(
venv
diff --git a/setup.py b/setup.py
index b6dfab6f2..149ce230e 100644
--- a/setup.py
+++ b/setup.py
@@ -38,6 +38,7 @@
"viewer": [
'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"'
],
+ "mf4": ["asammdf>=6.0.0"],
}
setup(
diff --git a/test/data/example_data.py b/test/data/example_data.py
index 0fa70993a..592556926 100644
--- a/test/data/example_data.py
+++ b/test/data/example_data.py
@@ -33,48 +33,59 @@ def sort_messages(messages):
[
Message(
# empty
+ timestamp=1e-4,
),
Message(
# only data
- data=[0x00, 0x42]
+ timestamp=2e-4,
+ data=[0x00, 0x42],
),
Message(
# no data
+ timestamp=3e-4,
arbitration_id=0xAB,
is_extended_id=False,
),
Message(
# no data
+ timestamp=4e-4,
arbitration_id=0x42,
is_extended_id=True,
),
Message(
# no data
- arbitration_id=0xABCDEF
+ timestamp=5e-4,
+ arbitration_id=0xABCDEF,
),
Message(
# empty data
- data=[]
+ timestamp=6e-4,
+ data=[],
),
Message(
# empty data
- data=[0xFF, 0xFE, 0xFD]
+ timestamp=7e-4,
+ data=[0xFF, 0xFE, 0xFD],
),
Message(
# with channel as integer
- channel=0
+ timestamp=8e-4,
+ channel=0,
),
Message(
# with channel as integer
- channel=42
+ timestamp=9e-4,
+ channel=42,
),
Message(
# with channel as string
- channel="vcan0"
+ timestamp=10e-4,
+ channel="vcan0",
),
Message(
# with channel as string
- channel="awesome_channel"
+ timestamp=11e-4,
+ channel="awesome_channel",
),
Message(
arbitration_id=0xABCDEF,
@@ -109,10 +120,10 @@ def sort_messages(messages):
TEST_MESSAGES_CAN_FD = sort_messages(
[
- Message(is_fd=True, data=range(64)),
- Message(is_fd=True, data=range(8)),
- Message(is_fd=True, data=range(8), bitrate_switch=True),
- Message(is_fd=True, data=range(8), error_state_indicator=True),
+ Message(timestamp=12e-4, is_fd=True, data=range(64)),
+ Message(timestamp=13e-4, is_fd=True, data=range(8)),
+ Message(timestamp=14e-4, is_fd=True, data=range(8), bitrate_switch=True),
+ Message(timestamp=15e-4, is_fd=True, data=range(8), error_state_indicator=True),
]
)
diff --git a/test/logformats_test.py b/test/logformats_test.py
index d3b0eb015..31903f84b 100644
--- a/test/logformats_test.py
+++ b/test/logformats_test.py
@@ -36,36 +36,61 @@
logging.basicConfig(level=logging.DEBUG)
+try:
+ import asammdf
+except ModuleNotFoundError:
+ asammdf = None
+
class ReaderWriterExtensionTest(unittest.TestCase):
- message_writers_and_readers = {}
- for suffix, writer in can.Logger.message_writers.items():
- message_writers_and_readers[suffix] = (
- writer,
- can.LogReader.message_readers.get(suffix),
- )
+ def _get_suffix_case_variants(self, suffix):
+ return [
+ suffix.upper(),
+ suffix.lower(),
+ f"can.msg.ext{suffix}",
+ "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]),
+ ]
- def test_extension_matching(self):
- for suffix, (writer, reader) in self.message_writers_and_readers.items():
- suffix_variants = [
- suffix.upper(),
- suffix.lower(),
- f"can.msg.ext{suffix}",
- "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]),
- ]
- for suffix_variant in suffix_variants:
- tmp_file = tempfile.NamedTemporaryFile(
- suffix=suffix_variant, delete=False
- )
- tmp_file.close()
- try:
+ def _test_extension(self, suffix):
+ WriterType = can.Logger.message_writers.get(suffix)
+ ReaderType = can.LogReader.message_readers.get(suffix)
+ for suffix_variant in self._get_suffix_case_variants(suffix):
+ tmp_file = tempfile.NamedTemporaryFile(suffix=suffix_variant, delete=False)
+ tmp_file.close()
+ try:
+ if WriterType:
with can.Logger(tmp_file.name) as logger:
- assert type(logger) == writer
- if reader is not None:
- with can.LogReader(tmp_file.name) as player:
- assert type(player) == reader
- finally:
- os.remove(tmp_file.name)
+ assert type(logger) == WriterType
+ if ReaderType:
+ with can.LogReader(tmp_file.name) as player:
+ assert type(player) == ReaderType
+ finally:
+ os.remove(tmp_file.name)
+
+ def test_extension_matching_asc(self):
+ self._test_extension(".asc")
+
+ def test_extension_matching_blf(self):
+ self._test_extension(".blf")
+
+ def test_extension_matching_csv(self):
+ self._test_extension(".csv")
+
+ def test_extension_matching_db(self):
+ self._test_extension(".db")
+
+ def test_extension_matching_log(self):
+ self._test_extension(".log")
+
+ def test_extension_matching_txt(self):
+ self._test_extension(".txt")
+
+ def test_extension_matching_mf4(self):
+ try:
+ self._test_extension(".mf4")
+ except NotImplementedError:
+ if asammdf is not None:
+ raise
class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase, metaclass=ABCMeta):
@@ -708,6 +733,22 @@ def _setup_instance(self):
)
+@unittest.skipIf(asammdf is None, "MF4 is unavailable")
+class TestMF4FileFormat(ReaderWriterTest):
+ """Tests can.MF4Writer and can.MF4Reader"""
+
+ def _setup_instance(self):
+ super()._setup_instance_helper(
+ can.MF4Writer,
+ can.MF4Reader,
+ binary_file=True,
+ check_comments=False,
+ preserves_channel=False,
+ allowed_timestamp_delta=1e-4,
+ adds_default_channel=0,
+ )
+
+
class TestSqliteDatabaseFormat(ReaderWriterTest):
"""Tests can.SqliteWriter and can.SqliteReader"""
diff --git a/tox.ini b/tox.ini
index 96cc82425..a41db4248 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,6 +11,7 @@ deps =
hypothesis~=6.35.0
pyserial~=3.5
parameterized~=0.8
+ asammdf>=6.0;platform_python_implementation=="CPython" and python_version < "3.12"
commands =
pytest {posargs}
From 6e5df1ddafad8c1fb427ee92c900152b260d6628 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Mon, 3 Apr 2023 15:34:12 +0200
Subject: [PATCH 248/475] mf4 followup (#1555)
---
can/io/player.py | 17 ++---------------
1 file changed, 2 insertions(+), 15 deletions(-)
diff --git a/can/io/player.py b/can/io/player.py
index 98a556b62..e4db0e167 100644
--- a/can/io/player.py
+++ b/can/io/player.py
@@ -8,7 +8,6 @@
import time
import typing
-import typing_extensions
from pkg_resources import iter_entry_points
from ..message import Message
@@ -18,22 +17,10 @@
from .canutils import CanutilsLogReader
from .csv import CSVReader
from .generic import MessageReader
+from .mf4 import MF4Reader
from .sqlite import SqliteReader
from .trc import TRCReader
-MF4Reader: typing.Optional[typing.Type[MessageReader]]
-try:
- from .mf4 import MF4Reader
-except ImportError:
- MF4Reader = None
-
-
-_OPTIONAL_READERS: typing_extensions.Final[
- typing.Dict[str, typing.Type[MessageReader]]
-] = {}
-if MF4Reader:
- _OPTIONAL_READERS[".mf4"] = MF4Reader
-
class LogReader(MessageReader):
"""
@@ -73,8 +60,8 @@ class LogReader(MessageReader):
".csv": CSVReader,
".db": SqliteReader,
".log": CanutilsLogReader,
+ ".mf4": MF4Reader,
".trc": TRCReader,
- **_OPTIONAL_READERS,
}
@staticmethod
From e0155c781eb6b0ede5e52e939e2d2f006f100d8f Mon Sep 17 00:00:00 2001
From: Yannis
Date: Tue, 4 Apr 2023 16:06:12 +0300
Subject: [PATCH 249/475] Optional dependency and docs for the CANine interface
(#1556)
* optional dependency and docs for the CANine interface
* use alphabetical order
---
doc/plugin-interface.rst | 3 +++
setup.py | 1 +
2 files changed, 4 insertions(+)
diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst
index bab8c85a9..4a08ee9a7 100644
--- a/doc/plugin-interface.rst
+++ b/doc/plugin-interface.rst
@@ -65,6 +65,8 @@ The table below lists interface drivers that can be added by installing addition
+----------------------------+-------------------------------------------------------+
| Name | Description |
+============================+=======================================================+
+| `python-can-canine`_ | CAN Driver for the CANine CAN interface |
++----------------------------+-------------------------------------------------------+
| `python-can-cvector`_ | Cython based version of the 'VectorBus' |
+----------------------------+-------------------------------------------------------+
| `python-can-remote`_ | CAN over network bridge |
@@ -72,6 +74,7 @@ The table below lists interface drivers that can be added by installing addition
| `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) |
+----------------------------+-------------------------------------------------------+
+.. _python-can-canine: https://github.com/tinymovr/python-can-canine
.. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector
.. _python-can-remote: https://github.com/christiansandberg/python-can-remote
.. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim
diff --git a/setup.py b/setup.py
index 149ce230e..65298b072 100644
--- a/setup.py
+++ b/setup.py
@@ -35,6 +35,7 @@
"pcan": ["uptime~=3.0.1"],
"remote": ["python-can-remote"],
"sontheim": ["python-can-sontheim>=0.1.2"],
+ "canine": ["python-can-canine>=0.2.2"],
"viewer": [
'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"'
],
From fd8d0766d29c004865a18a12a2fb58cb52e7b435 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 9 Apr 2023 16:57:15 +0200
Subject: [PATCH 250/475] add modules to __all__ (#1558)
---
can/__init__.py | 26 ++++++++++++++++++++------
can/interfaces/__init__.py | 28 ++++++++++++++++++++++++++++
can/io/__init__.py | 11 +++++++++++
3 files changed, 59 insertions(+), 6 deletions(-)
diff --git a/can/__init__.py b/can/__init__.py
index 28416fb61..3983c7495 100644
--- a/can/__init__.py
+++ b/can/__init__.py
@@ -17,7 +17,6 @@
"BitTimingFd",
"BLFReader",
"BLFWriter",
- "broadcastmanager",
"BufferedReader",
"Bus",
"BusABC",
@@ -32,8 +31,6 @@
"CSVReader",
"CSVWriter",
"CyclicSendTaskABC",
- "detect_available_configs",
- "interface",
"LimitedDurationCyclicSendTaskABC",
"Listener",
"Logger",
@@ -47,17 +44,34 @@
"Printer",
"RedirectReader",
"RestartableCyclicTaskABC",
- "set_logging_level",
"SizedRotatingLogger",
"SqliteReader",
"SqliteWriter",
"ThreadSafeBus",
- "typechecking",
"TRCFileVersion",
"TRCReader",
"TRCWriter",
- "util",
"VALID_INTERFACES",
+ "bit_timing",
+ "broadcastmanager",
+ "bus",
+ "ctypesutil",
+ "detect_available_configs",
+ "exceptions",
+ "interface",
+ "interfaces",
+ "listener",
+ "logconvert",
+ "log",
+ "logger",
+ "message",
+ "notifier",
+ "player",
+ "set_logging_level",
+ "thread_safe_bus",
+ "typechecking",
+ "util",
+ "viewer",
]
log = logging.getLogger("can")
diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py
index 5089dbf47..b14914230 100644
--- a/can/interfaces/__init__.py
+++ b/can/interfaces/__init__.py
@@ -5,6 +5,34 @@
import sys
from typing import Dict, Tuple, cast
+__all__ = [
+ "BACKENDS",
+ "VALID_INTERFACES",
+ "canalystii",
+ "cantact",
+ "etas",
+ "gs_usb",
+ "ics_neovi",
+ "iscan",
+ "ixxat",
+ "kvaser",
+ "neousys",
+ "nican",
+ "nixnet",
+ "pcan",
+ "robotell",
+ "seeedstudio",
+ "serial",
+ "slcan",
+ "socketcan",
+ "socketcand",
+ "systec",
+ "udp_multicast",
+ "usb2can",
+ "vector",
+ "virtual",
+]
+
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, str]] = {
"kvaser": ("can.interfaces.kvaser", "KvaserBus"),
diff --git a/can/io/__init__.py b/can/io/__init__.py
index 05b8619f2..263bbe235 100644
--- a/can/io/__init__.py
+++ b/can/io/__init__.py
@@ -25,6 +25,17 @@
"TRCFileVersion",
"TRCReader",
"TRCWriter",
+ "asc",
+ "blf",
+ "canutils",
+ "csv",
+ "generic",
+ "logger",
+ "mf4",
+ "player",
+ "printer",
+ "sqlite",
+ "trc",
]
# Generic
From d62c97f3963a92effd2a17e763580569d6b4f3f2 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 9 Apr 2023 17:22:33 +0200
Subject: [PATCH 251/475] improve slcanTestCase robustness (#1559)
---
test/test_slcan.py | 34 +++++++++++++++++++++-------------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/test/test_slcan.py b/test/test_slcan.py
index 774a2dec5..a2f8f5d15 100644
--- a/test/test_slcan.py
+++ b/test/test_slcan.py
@@ -1,6 +1,9 @@
#!/usr/bin/env python
import unittest
+from typing import cast
+
+import serial
import can
@@ -15,16 +18,17 @@
https://realpython.com/pypy-faster-python/#it-doesnt-work-well-with-c-extensions
"""
-TIMEOUT = 0.5 if IS_PYPY else 0.001 # 0.001 is the default set in slcanBus
+TIMEOUT = 0.5 if IS_PYPY else 0.01 # 0.001 is the default set in slcanBus
class slcanTestCase(unittest.TestCase):
def setUp(self):
- self.bus = can.Bus(
- "loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT
+ self.bus = cast(
+ can.interfaces.slcan.slcanBus,
+ can.Bus("loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT),
)
- self.serial = self.bus.serialPortOrig
- self.serial.read(self.serial.in_waiting)
+ self.serial = cast(serial.Serial, self.bus.serialPortOrig)
+ self.serial.reset_input_buffer()
def tearDown(self):
self.bus.shutdown()
@@ -44,8 +48,9 @@ def test_send_extended(self):
arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55]
)
self.bus.send(msg)
- data = self.serial.read(self.serial.in_waiting)
- self.assertEqual(data, b"T12ABCDEF2AA55\r")
+ expected = b"T12ABCDEF2AA55\r"
+ data = self.serial.read(len(expected))
+ self.assertEqual(data, expected)
def test_recv_standard(self):
self.serial.write(b"t4563112233\r")
@@ -62,8 +67,9 @@ def test_send_standard(self):
arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33]
)
self.bus.send(msg)
- data = self.serial.read(self.serial.in_waiting)
- self.assertEqual(data, b"t4563112233\r")
+ expected = b"t4563112233\r"
+ data = self.serial.read(len(expected))
+ self.assertEqual(data, expected)
def test_recv_standard_remote(self):
self.serial.write(b"r1238\r")
@@ -79,8 +85,9 @@ def test_send_standard_remote(self):
arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8
)
self.bus.send(msg)
- data = self.serial.read(self.serial.in_waiting)
- self.assertEqual(data, b"r1238\r")
+ expected = b"r1238\r"
+ data = self.serial.read(len(expected))
+ self.assertEqual(data, expected)
def test_recv_extended_remote(self):
self.serial.write(b"R12ABCDEF6\r")
@@ -96,8 +103,9 @@ def test_send_extended_remote(self):
arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6
)
self.bus.send(msg)
- data = self.serial.read(self.serial.in_waiting)
- self.assertEqual(data, b"R12ABCDEF6\r")
+ expected = b"R12ABCDEF6\r"
+ data = self.serial.read(len(expected))
+ self.assertEqual(data, expected)
def test_partial_recv(self):
self.serial.write(b"T12ABCDEF")
From 39a396541cfd7f0e160bc1ddaf3edde10bdc6928 Mon Sep 17 00:00:00 2001
From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com>
Date: Sun, 9 Apr 2023 20:03:21 +0200
Subject: [PATCH 252/475] Update CHANGELOG.md for 4.2.0 (#1552)
* update CHANGELOG.md
* Commit suggestion 1
Co-authored-by: Brian Thorne
* Commit suggestion 2
Co-authored-by: Brian Thorne
* Commit suggestion 3
Co-authored-by: Brian Thorne