From c31b5bade36f4076f75fad4561968c348e245644 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:23 +0800 Subject: [PATCH 01/15] Add gs_usb support --- can/interfaces/gs_usb.py | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 can/interfaces/gs_usb.py diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py new file mode 100644 index 000000000..e424a8643 --- /dev/null +++ b/can/interfaces/gs_usb.py @@ -0,0 +1,107 @@ +from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union + +from gs_usb.gs_usb import GsUsb +from gs_usb.gs_usb_frame import GsUsbFrame +from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC +import can +import usb + + +class GsUsbBus(can.BusABC): + def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): + """ + :param channel: usb device name + :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 can.CanError("Can not find device {}".format(channel)) + self.gs_usb = gs_usb + self.channel_info = channel + + self.gs_usb.set_bitrate(bitrate) + self.gs_usb.start() + + super().__init__(channel=channel, can_filters=can_filters, **kwargs) + + def send(self, msg: can.Message, timeout: Optional[float] = None): + """Transmit a message to the CAN bus. + + :param Message msg: A message object. + :param timeout: timeout is not used here + + :raises can.CanError: + if the message could not be sent + """ + can_id = msg.arbitration_id + + if msg.is_extended_id: + can_id = can_id | CAN_EFF_FLAG + + if msg.is_remote_frame: + can_id = can_id | CAN_RTR_FLAG + + if msg.is_error_frame: + can_id = can_id | CAN_ERR_FLAG + + frame = GsUsbFrame() + frame.can_id = can_id + frame.can_dlc = msg.dlc + frame.timestamp_us = int(msg.timestamp * 1000000) + msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) + frame.data = list(msg.data) + + self.gs_usb.send(frame) + + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[can.Message], bool]: + """ + Read a message from the bus and tell whether it was filtered. + This methods may be called by :meth:`~can.BusABC.recv` + to read a message multiple times if the filters set by + :meth:`~can.BusABC.set_filters` do not match and the call has + not yet timed out. + + :param float timeout: seconds to wait for a message, + see :meth:`~can.BusABC.send` + 0 and None will be converted to minimum value 1ms. + float will be finally converted to integer. + + :return: + 1. a message that was read or None on timeout + 2. a bool that is True if message filtering has already + been done and else False. In this interface it is always False + since filtering is not available + + :raises can.CanError: + if an error occurred while reading + """ + frame = GsUsbFrame() + + # Do not set timeout as None or zero here to avoid blocking + timeout_ms = round(timeout * 1000) if timeout else 1 + if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms): + return None, False + + msg = can.Message() + msg.arbitration_id = frame.arbitration_id + msg.dlc = frame.can_dlc + msg.data = bytearray(frame.data)[0:msg.dlc] + msg.timestamp = frame.timestamp + msg.channel = self.channel_info + msg.is_extended_id = frame.is_extended_id + msg.is_remote_frame = frame.is_remote_frame + msg.is_error_frame = frame.is_error_frame + msg.is_rx = True + return msg, False + + def shutdown(self): + """ + Called to carry out any interface specific cleanup required + in shutting down a bus. + """ + self.gs_usb.stop() From 6972ab873e32022c010cc9e774469c541441a6b8 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:41 +0800 Subject: [PATCH 02/15] Enable gs_usb in interface list --- can/interfaces/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1fa3f5b4b..2fb1afce9 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -25,6 +25,7 @@ "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), "cantact": ("can.interfaces.cantact", "CantactBus"), + "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), } BACKENDS.update( From ab9d4a3cdce59ae6dadb517cbe444071cd7b60a0 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:58 +0800 Subject: [PATCH 03/15] Add install requires for gs_usb interface --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e5582e0a5..65ddaae97 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], "cantact": ["cantact>=0.0.7"], + "gs_usb": ["gs_usb>=0.2.1"], } setup( From 9f7b60a72fb89b19cbb93e125b46ca8cdc45917d Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:06:15 +0800 Subject: [PATCH 04/15] Add documentation for gs_usb interface --- doc/interfaces.rst | 1 + doc/interfaces/gs_usb.rst | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100755 doc/interfaces/gs_usb.rst diff --git a/doc/interfaces.rst b/doc/interfaces.rst index bd7a0d1df..2541f4610 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -27,6 +27,7 @@ The available interfaces are: interfaces/canalystii interfaces/systec interfaces/seeedstudio + interfaces/gs_usb 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. diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst new file mode 100755 index 000000000..0d6fe9ac5 --- /dev/null +++ b/doc/interfaces/gs_usb.rst @@ -0,0 +1,29 @@ +.. _gs_usb: + +CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces +======================== + +Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. + +Install: ``pip install "python-can[gs_usb]"`` +Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` + + +Supported devices +----------------- + +Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces such as candleLight, canable, cantact, etc. + + +Supported platform +------------------ + +Windows, Linux and Mac. + +Note: Since ``pyusb``` is used, ``libusb-win32`` usb driver is required to install in Windows + +Bus +--- + +.. autoclass:: can.interfaces.gs_usb.GsUsbBus + :members: From 470c50e84e6b02be095223da45f2efb0acd5c771 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:36:02 +0800 Subject: [PATCH 05/15] Reformat code by black --- can/interfaces/gs_usb.py | 2 +- doc/interfaces/gs_usb.rst | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index e424a8643..0214eff99 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -90,7 +90,7 @@ def _recv_internal( msg = can.Message() msg.arbitration_id = frame.arbitration_id msg.dlc = frame.can_dlc - msg.data = bytearray(frame.data)[0:msg.dlc] + msg.data = bytearray(frame.data)[0 : msg.dlc] msg.timestamp = frame.timestamp msg.channel = self.channel_info msg.is_extended_id = frame.is_extended_id diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 0d6fe9ac5..c733e00e8 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -1,11 +1,12 @@ .. _gs_usb: CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces -======================== +================================================================================================================== Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. Install: ``pip install "python-can[gs_usb]"`` + Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` @@ -20,7 +21,7 @@ Supported platform Windows, Linux and Mac. -Note: Since ``pyusb``` is used, ``libusb-win32`` usb driver is required to install in Windows +Note: Since ``pyusb`` is used, ``libusb-win32`` usb driver is required to be installed in Windows Bus --- From 80f8a9fa12035f906f26765d33cf903f3effc569 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 10:01:55 +0800 Subject: [PATCH 06/15] Remove additional comment since float is actually accepted as timeout --- can/interfaces/gs_usb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 0214eff99..8460a88f4 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -69,7 +69,6 @@ def _recv_internal( :param float timeout: seconds to wait for a message, see :meth:`~can.BusABC.send` 0 and None will be converted to minimum value 1ms. - float will be finally converted to integer. :return: 1. a message that was read or None on timeout From c4ccdd03143a04c00b3f6346bc40bc4a5cb085f2 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 10:23:57 +0800 Subject: [PATCH 07/15] Pass can message fields as named arguments --- can/interfaces/gs_usb.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 8460a88f4..872c75056 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -86,16 +86,18 @@ def _recv_internal( if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms): return None, False - msg = can.Message() - msg.arbitration_id = frame.arbitration_id - msg.dlc = frame.can_dlc - msg.data = bytearray(frame.data)[0 : msg.dlc] - msg.timestamp = frame.timestamp - msg.channel = self.channel_info - msg.is_extended_id = frame.is_extended_id - msg.is_remote_frame = frame.is_remote_frame - msg.is_error_frame = frame.is_error_frame - msg.is_rx = True + msg = can.Message( + timestamp=frame.timestamp, + arbitration_id=frame.arbitration_id, + is_extended_id=frame.can_dlc, + 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 : msg.dlc], + is_rx=True, + ) + return msg, False def shutdown(self): From 74e3272d0fc2361677a62fdc1dd7f614fc2fb9f1 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 11:36:24 +0800 Subject: [PATCH 08/15] Update docs for complete setup for a first time user --- doc/interfaces/gs_usb.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index c733e00e8..5053f1d9b 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -7,7 +7,15 @@ Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN dev Install: ``pip install "python-can[gs_usb]"`` -Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` +Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` as shown below: + +:: + + 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) Supported devices From ad49fca3767ce5efab668ef9ddc7af81d015418e Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 11:40:35 +0800 Subject: [PATCH 09/15] Update can/interfaces/gs_usb.py Co-authored-by: Brian Thorne --- can/interfaces/gs_usb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 872c75056..da30e8ddf 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -47,11 +47,13 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): if msg.is_error_frame: can_id = can_id | CAN_ERR_FLAG + # Pad message data + msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) + frame = GsUsbFrame() frame.can_id = can_id frame.can_dlc = msg.dlc frame.timestamp_us = int(msg.timestamp * 1000000) - msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) frame.data = list(msg.data) self.gs_usb.send(frame) From 52b53b21111ae989db07a522dfe6ad03138f8a3b Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 12:57:04 +0800 Subject: [PATCH 10/15] Catch usb error and re-raise as CanError --- can/interfaces/gs_usb.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index da30e8ddf..23c7e7f89 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -56,7 +56,10 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): frame.timestamp_us = int(msg.timestamp * 1000000) frame.data = list(msg.data) - self.gs_usb.send(frame) + try: + self.gs_usb.send(frame) + except usb.core.USBError: + raise can.CanError("The message can not be sent") def _recv_internal( self, timeout: Optional[float] From 5565e2bf7f89ebaf67090ece5dbc8b10f9d36d10 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 13:01:14 +0800 Subject: [PATCH 11/15] Add logging for future use --- can/interfaces/gs_usb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 23c7e7f89..fa6e7d148 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -5,8 +5,11 @@ from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC import can import usb +import logging +logger = logging.getLogger(__name__) + class GsUsbBus(can.BusABC): def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): """ From e90535ae906c8e7131ddc203edaba01b95f523a9 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 13:07:14 +0800 Subject: [PATCH 12/15] Update docs for expected behavior for send method --- can/interfaces/gs_usb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index fa6e7d148..cea33d089 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -34,7 +34,8 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): """Transmit a message to the CAN bus. :param Message msg: A message object. - :param timeout: timeout is not used here + :param timeout: timeout is not supported. + The function won't return until message is sent or exception is raised. :raises can.CanError: if the message could not be sent From 676dd7c9613a7272d1d138c3791bc2f5933a5ac6 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 14:24:05 +0800 Subject: [PATCH 13/15] Add supplementary info on gs_usb --- doc/interfaces/gs_usb.rst | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 5053f1d9b..88646547a 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -1,9 +1,9 @@ .. _gs_usb: -CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces +CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces ================================================================================================================== -Windows/Linux/Mac CAN driver based on 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]"`` @@ -29,7 +29,21 @@ Supported platform Windows, Linux and Mac. -Note: Since ``pyusb`` is used, ``libusb-win32`` usb driver is required to be installed in Windows +Note: Since ``pyusb`` with ```libusb0``` as backend is used, ``libusb-win32`` usb driver is required to be installed in Windows. + + +Supplementary Info on ``gs_usb`` +----------------------------------- + +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. + +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. Bus --- From ec5740ab4d701e23c8f34c624747c50efa5bf7e0 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 14:41:04 +0800 Subject: [PATCH 14/15] Reformat code by black --- can/interfaces/gs_usb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index cea33d089..dadddc267 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) + class GsUsbBus(can.BusABC): def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): """ @@ -53,7 +54,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): # Pad message data msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) - + frame = GsUsbFrame() frame.can_id = can_id frame.can_dlc = msg.dlc From 751dfa40c0da29c55c828285ed8227b4f1e5e902 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 15:56:28 +0800 Subject: [PATCH 15/15] Fix bug where msg is referenced befor assignment --- can/interfaces/gs_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index dadddc267..ff6ba634e 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -104,7 +104,7 @@ def _recv_internal( is_error_frame=frame.is_error_frame, channel=self.channel_info, dlc=frame.can_dlc, - data=bytearray(frame.data)[0 : msg.dlc], + data=bytearray(frame.data)[0 : frame.can_dlc], is_rx=True, )