From 785b53b41697041ef7fb9a35f315755b03d9d6a3 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Wed, 12 Jan 2022 18:55:30 +0800 Subject: [PATCH 01/17] Add CanBitRateError --- can/__init__.py | 1 + can/exceptions.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/can/__init__.py b/can/__init__.py index c95b19ebf..ccc095695 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -22,6 +22,7 @@ CanInitializationError, CanOperationError, CanTimeoutError, + CanBitRateError, ) from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync diff --git a/can/exceptions.py b/can/exceptions.py index e8731737c..f5c87bad6 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -9,6 +9,7 @@ +-- CanInitializationError +-- CanOperationError +-- CanTimeoutError + +-- CanBitRateError Keep in mind that some functions and methods may raise different exceptions. For example, validating typical arguments and parameters might result in a @@ -102,6 +103,15 @@ class CanTimeoutError(CanError, TimeoutError): """ +class CanBitRateError(CanError): + """Indicates the invalid / unsupported bitrate. + + Example scenarios: + - Invalid / unsupported bitrate on bus + - Can't convert between bit timing and bitrate + """ + + @contextmanager def error_check( error_message: Optional[str] = None, From 7970dadaefab9ef566ab8e58f327fd7633eac08c Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Wed, 12 Jan 2022 19:15:31 +0800 Subject: [PATCH 02/17] Add ZLG interface --- can/interfaces/__init__.py | 1 + can/interfaces/zlg/__init__.py | 12 ++ can/interfaces/zlg/bus.py | 179 ++++++++++++++++++++++++ can/interfaces/zlg/timing.py | 42 ++++++ can/interfaces/zlg/vci.py | 242 +++++++++++++++++++++++++++++++++ doc/configuration.rst | 3 + doc/interfaces.rst | 1 + doc/interfaces/zlg.rst | 76 +++++++++++ test/test_interface_zlg.py | 83 +++++++++++ 9 files changed, 639 insertions(+) create mode 100644 can/interfaces/zlg/__init__.py create mode 100644 can/interfaces/zlg/bus.py create mode 100644 can/interfaces/zlg/timing.py create mode 100644 can/interfaces/zlg/vci.py create mode 100644 doc/interfaces/zlg.rst create mode 100644 test/test_interface_zlg.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 65a9cb859..46029f978 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -27,6 +27,7 @@ "neousys": ("can.interfaces.neousys", "NeousysBus"), "etas": ("can.interfaces.etas", "EtasBus"), "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), + "zlg": ("can.interfaces.zlg", "ZlgCanBus"), } try: diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py new file mode 100644 index 000000000..ffdce1a21 --- /dev/null +++ b/can/interfaces/zlg/__init__.py @@ -0,0 +1,12 @@ +''' +Unofficial ZLG USBCAN/USBCANFD implementation for Linux +''' + +__version__ = '1.0' +__date__ = '20220112' +__author__ = 'Keelung.Yang@flex.com' +__history__ = ''' + 1. Initial creation. Keelung.Yang@flex.com, 2022-01-06 +''' + +from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py new file mode 100644 index 000000000..64825b696 --- /dev/null +++ b/can/interfaces/zlg/bus.py @@ -0,0 +1,179 @@ +import time +import ctypes + +from can import BitTiming, BusABC, BusState, Message +from can import CanInitializationError, CanOperationError + +from .vci import * +from .timing import ZlgBitTiming + + +DeviceType = ZCAN_DEVICE.USBCANFD_200U + + +class ZlgCanBus(BusABC): + def __init__(self, channel, device=0, tres=True, **kwargs): + """ + :param channel: channel index, [0, 1,,,] + :param device: device index, [0, 1,,,] + :param tres: enable/disable termination resistor on specified channel + """ + bitrate = int(kwargs.get('bitrate', 500000)) + data_bitrate = int(kwargs.get('data_bitrate', bitrate)) + self.channel_info = \ + f'{self.__class__.__name__}{device}:{channel}@{bitrate}' + if bitrate != data_bitrate: + self.channel_info += f'/{data_bitrate}' + self._dev_type = ZCAN_DEVICE_TYPE(DeviceType.value) + self._dev_index = ZCAN_DEVICE_INDEX(int(device)) + self._dev_channel = ZCAN_CHANNEL(int(channel)) + self._opened = self.open(bitrate, data_bitrate) + if not self._opened: + raise CanInitializationError(f'Failed to open {self.channel_info}') + self.tres = bool(tres) + super().__init__(int(channel), **kwargs) + + @property + def tres(self): + '''Termination resistor''' + return self._tres + + @tres.setter + def tres(self, value): + self._tres = bool(value) + if not vci_channel_enable_tres( + self._dev_type, self._dev_index, self._dev_channel, self._tres + ): + raise CanOperationError( + f'Failed to {"enable" if value else "disable"} ' + f'termination resistor for {self.channel_info} !' + ) + + @property + def state(self): + err_msg = ZCAN_ERR_MSG() + if not vci_channel_read_info( + self._dev_type, self._dev_index, self._dev_channel, err_msg + ): + raise CanOperationError( + f'Failed to read CAN{self._dev_channel} status!' + ) + if err_msg.info.err: + if err_msg.info.est: + return BusState.ERROR + else: + return BusState.ACTIVE + return None # https://github.com/hardbyte/python-can/issues/736 + + @state.setter + def state(self, value): + raise NotImplementedError() + + def _from_raw(self, raw): + return Message( + timestamp = raw.header.ts, + arbitration_id = raw.header.id, + is_fd = bool(raw.header.info.fmt), + is_extended_id = bool(raw.header.info.sef), + is_remote_frame = bool(raw.header.info.sdf), + is_error_frame = bool(raw.header.info.err), + bitrate_switch = bool(raw.header.info.brs), + dlc = raw.header.len, + data = bytes(raw.dat[:raw.header.len]), + channel = raw.header.chn, + ) + + def _to_raw(self, msg): + info = ZCAN_MSG_INFO( + txm = False, + fmt = msg.is_fd, + sdf = msg.is_remote_frame, + sef = msg.is_extended_id, + err = msg.is_error_frame, + brs = msg.bitrate_switch, + est = msg.error_state_indicator, + # pad + ) + header = ZCAN_MSG_HDR( + ts = c_uint32(int(time.time())), + id = msg.arbitration_id, + info = info, + chn = self._dev_channel.value, + len = msg.dlc + ) + if msg.is_fd: + raw = ZCAN_FD_MSG(header=header) + else: + raw = ZCAN_20_MSG(header=header) + ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) + return raw + + def _recv_internal(self, timeout): + timeout = c_uint32(int((timeout or 0)*1000)) + if vci_can_get_recv_num( + self._dev_type, self._dev_index, self._dev_channel + ) > 0: + rx_buf = (ZCAN_20_MSG * 1)() + if vci_can_recv( + self._dev_type, self._dev_index, self._dev_channel, + rx_buf, len(rx_buf), timeout + ) > 0: + return self._from_raw(rx_buf[0]), self.filters is None + else: + raise CanOperationError('Failed to receive message!') + if vci_canfd_get_recv_num( + self._dev_type, self._dev_index, self._dev_channel + ) > 0: + rx_buf = (ZCAN_FD_MSG * 1)() + if vci_canfd_recv( + self._dev_type, self._dev_index, self._dev_channel, + rx_buf, len(rx_buf), timeout + ) > 0: + return self._from_raw(rx_buf[0]), self.filters is None + else: + raise CanOperationError('Failed to receive CANFD message!') + time.sleep(.0001) # Avoid high CPU usage if no message received + return None, self.filters is None + + def send(self, msg, timeout=None) -> None: + timeout = c_uint32(int((timeout or 0)*1000)) + if msg.is_fd: + tx_buf = (ZCAN_FD_MSG * 1)() + tx_buf[0] = self._to_raw(msg) + if not vci_canfd_send( + self._dev_type, self._dev_index, self._dev_channel, + tx_buf, len(tx_buf) + ): + raise CanOperationError( + f'Failed to send CANFD message {msg.arbitration_id:03X}!' + ) + else: + tx_buf = (ZCAN_20_MSG * 1)() + tx_buf[0] = self._to_raw(msg) + if not vci_can_send( + self._dev_type, self._dev_index, self._dev_channel, + tx_buf, len(tx_buf) + ): + raise CanOperationError( + f'Failed to send CAN message {msg.arbitration_id:03X}!' + ) + + def open(self, bitrate, data_bitrate): + timing = ZlgBitTiming(self._dev_type.value) + if not vci_device_open(self._dev_type, self._dev_index): + return False + if not vci_channel_open( + self._dev_type, self._dev_index, self._dev_channel, + timing.f_clock, + timing.timing(bitrate), + timing.timing(data_bitrate) + ): + vci_device_close(self._dev_type, self._dev_index) + return False + else: + return True + + def shutdown(self) -> None: + super().shutdown() + vci_channel_close(self._dev_type, self._dev_index, self._dev_channel) + vci_device_close(self._dev_type, self._dev_index) diff --git a/can/interfaces/zlg/timing.py b/can/interfaces/zlg/timing.py new file mode 100644 index 000000000..5533222de --- /dev/null +++ b/can/interfaces/zlg/timing.py @@ -0,0 +1,42 @@ +import time +import ctypes + +from can import BitTiming, CanInitializationError, CanBitRateError +from .vci import ZCAN_BIT_TIMING, ZCAN_DEVICE + + +# Official timing calculation +# https://manual.zlg.cn/web/#/188/6981 +class ZlgBitTiming(BitTiming): + def __init__(self, device, **kwargs): + try: + self._device = ZCAN_DEVICE(device) + except: + raise CanInitializationError(f'Unsupported ZLG CAN device {device} !') + if self._device in ( + ZCAN_DEVICE.USBCAN, + ZCAN_DEVICE.USBCANFD_200U, + ): + kwargs.setdefault('f_clock', 60000000) + self.speeds = { + 125_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=31), + 250_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=15), + 500_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=7), + 1_000_000: ZCAN_BIT_TIMING(tseg1=46, tseg2=11, sjw=3, brp=0), + 2_000_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=1), + 4_000_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=0), + } + super().__init__(**kwargs) + + def timing(self, bitrate=None, force=False): + if bitrate in self.speeds: + return self.speeds[bitrate] + elif force: + return ZCAN_BIT_TIMING( + tseg1 = self.tseg1, + tseg2 = self.tseg2, + sjw = self.sjw, + brp = self.brp, + ) + else: + raise CanBitRateError(f'Unsupported {bitrate=}') diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py new file mode 100644 index 000000000..d0cdd5384 --- /dev/null +++ b/can/interfaces/zlg/vci.py @@ -0,0 +1,242 @@ +''' +Wrap libusbcanfd.so +''' + +####################################################################### +# For consistency, use the coding style and definitions +# as similar as USBCANFD_DEMO.py in Linux driver package +# https://manual.zlg.cn/web/#/146 +####################################################################### + +import logging + +from enum import unique, auto, Enum, Flag +from ctypes import c_ubyte, c_uint8, c_uint16, c_uint32 +from ctypes import cdll, byref, Structure + + +log = logging.getLogger('can.zlg') +lib = cdll.LoadLibrary('libusbcanfd.so') + +ZCAN_DEVICE_TYPE = c_uint32 +ZCAN_DEVICE_INDEX = c_uint32 +ZCAN_CHANNEL = c_uint32 + + +@unique +class ZCAN_DEVICE(Enum): + '''CAN Device Type + Only USBCAN / USBCANFD_200U supported now + For more infomation, please check + https://manual.zlg.cn/web/#/188/6984 + ''' + USBCAN = 4 + USBCANFD_200U = 33 + + +class ZCAN_MODE(Flag): + LISTEN_ONLY = auto() # 0: Normal, 1: Listen only + BOSCH = auto() # 0: ISO, 1: BOSCH + NORMAL = (~LISTEN_ONLY & ~BOSCH) & 0x03 + + +@unique +class ZCAN_REF(Enum): + FILTER = 0x14 # CAN Message Filter + TRES = 0x18 # Termination resistor + TX_TIMEOUT = 0x44 # Send timeout in ms + TTX_SETTING = 0x16 # Timing send settins + TTX_ENABLE = 0x17 # Timing send enable + + @property + def value(self) -> c_uint32: + return c_uint32(super().value) + + +class ZCAN_TRES(Structure): # Termination resistor + ON = 1 + OFF = 0 + _fields_=[ + ('enable', c_uint8) + ] + + +class ZCAN_MSG_INFO(Structure): + _fields_ = [ + ('txm', c_uint32, 4), # TXTYPE, 0: normal, 1: once, 2: self + ('fmt', c_uint32, 4), # 0:CAN2.0, 1:CANFD + ('sdf', c_uint32, 1), # 0: data frame, 1: remote frame + ('sef', c_uint32, 1), # 0: std frame, 1: ext frame + ('err', c_uint32, 1), # error flag + ('brs', c_uint32, 1), # bit-rate switch, 0: Not speed up, 1: speed up + ('est', c_uint32, 1), # error state + ('pad', c_uint32, 19) + ] + + +class ZCAN_MSG_HDR(Structure): + _fields_ = [ + ('ts', c_uint32), # timestamp + ('id', c_uint32), # can-id + ('info', ZCAN_MSG_INFO), + ('pad', c_uint16), + ('chn', c_uint8), # channel + ('len', c_uint8) # data length + ] + + +class ZCAN_20_MSG(Structure): + _fields_ = [ + ('header', ZCAN_MSG_HDR), + ('dat', c_ubyte*8) + ] + + +class ZCAN_FD_MSG(Structure): + _fields_ = [ + ('header', ZCAN_MSG_HDR), + ('dat', c_ubyte*64) + ] + + +class ZCAN_ERR_MSG(Structure): + _fields_ = [ + ('header', ZCAN_MSG_HDR), + ('dat', c_ubyte*8) + ] + + +class ZCAN_BIT_TIMING(Structure): + _fields_ = [ + ('tseg1', c_uint8), + ('tseg2', c_uint8), + ('sjw', c_uint8), + ('smp', c_uint8), # Not used + ('brp', c_uint16) + ] + + +class ZCANFD_INIT(Structure): + _fields_ = [ + ('clk', c_uint32), + ('mode', c_uint32), + ('atim', ZCAN_BIT_TIMING), + ('dtim', ZCAN_BIT_TIMING) + ] + + +def vci_device_open(type_, index) -> bool: + ret = lib.VCI_OpenDevice(type_, index) + if ret == 0: + log.error(f'Failed to open device {type_}:{index} !') + else: + log.info(f'Open device {type_}:{index} successfully!') + return ret != 0 + + +def vci_device_close(type_, index) -> bool: + ret = lib.VCI_CloseDevice(type_, index) + if ret == 0: + log.error(f'Failed to open device {type_}:{index}') + else: + log.info(f'Open device {type_}:{index} successfully') + return ret != 0 + + +def vci_channel_open(type_, index, channel, + clock, + atiming, + dtiming=None, + mode=ZCAN_MODE.NORMAL + ) -> bool: + init = ZCANFD_INIT() + init.mode = mode.value + init.clk = clock + init.atim = atiming + init.dtim = dtiming or atiming + ret = lib.VCI_InitCAN(type_, index, channel, byref(init)) + if ret == 0: + log.error(f'CH{channel.value}: Initialize failed') + else: + log.info(f'CH{channel.value}: Initialized') + ret = lib.VCI_StartCAN(type_, index, channel) + if ret == 0: + log.error(f'CH{channel.value}: Start failed') + else: + log.info(f'CH{channel.value}: Started') + return ret != 0 + +def vci_channel_close(type_, index, channel) -> bool: + ret = lib.VCI_ResetCAN(type_, index, channel) + if ret == 0: + log.error(f'CH{channel.value}: Close failed') + else: + log.info(f'CH{channel.value}: Closed') + return ret != 0 + + +def vci_channel_read_info(type_, index, channel, info) -> bool: + ret = lib.VCI_ReadErrInfo(type_, index, channel, byref(info)) + if ret == 0: + log.error(f'CH{channel.value}: Failed to read error infomation') + else: + log.info(f'CH{channel.value}: Read error infomation successfully') + return ret != 0 + + +def vci_channel_enable_tres(type_, index, channel, value) -> bool: + tres = ZCAN_TRES(enable=ZCAN_TRES.ON if value else ZCAN_TRES.OFF) + ret = lib.VCI_SetReference(type_, index, channel, ZCAN_REF.TRES.value, byref(tres)) + if ret == 0: + log.error(f'CH{channel.value}: Failed to {"enable" if value else "disable"} ' + f'termination resistor') + else: + log.info(f'CH{channel.value}: {"Enable" if value else "Disable"} ' + f'termination resistor successfully') + return ret != 0 + + +def vci_can_send(type_, index, channel, msgs, count) -> int: + ret = lib.VCI_Transmit(type_, index, channel, byref(msgs), count) + if ret == 0: + log.error(f'CH{channel.value}: Failed to send CAN message(s)') + else: + log.debug(f'CH{channel.value}: Sent {len(msgs)} CAN message(s)') + return ret + + +def vci_canfd_send(type_, index, channel, msgs, count) -> int: + ret = lib.VCI_TransmitFD(type_, index, channel, byref(msgs), count) + if ret == 0: + log.error(f'CH{channel.value}: Failed to send CANFD message(s)') + else: + log.debug(f'CH{channel.value}: Sent {len(msgs)} CANFD message(s)') + return ret + + +def vci_can_recv(type_, index, channel, msgs, count, timeout) -> int: + ret = lib.VCI_Receive(type_, index, channel, byref(msgs), count, timeout) + log.debug(f'CH{channel.value}: Received {len(msgs)} CAN message(s)') + return ret + + +def vci_canfd_recv(type_, index, channel, msgs, count, timeout) -> int: + ret = lib.VCI_ReceiveFD(type_, index, channel, byref(msgs), count, timeout) + log.debug(f'CH{channel.value}: Received {len(msgs)} CANFD message(s)') + return ret + + +def vci_can_get_recv_num(type_, index, channel) -> int: + ret = lib.VCI_GetReceiveNum(type_, index, channel) + log.debug(f'CH{channel.value}: {ret} CAN message(s) in FIFO') + return ret + + +def vci_canfd_get_recv_num(type_, index, channel) -> int: + if isinstance(channel, c_uint32): + channel = c_uint32(channel.value | 0x80000000) + else: + channel = c_uint32(int(channel) | 0x80000000) + ret = lib.VCI_GetReceiveNum(type_, index, channel) + log.debug(f'CH{channel.value&0x7FFF_FFFF}: {ret} CANFD message(s) in FIFO') + return ret diff --git a/doc/configuration.rst b/doc/configuration.rst index 9bda3030f..8c959179b 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -137,3 +137,6 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"systec"`` | :doc:`interfaces/systec` | +---------------------+-------------------------------------+ ++---------------------+-------------------------------------+ +| ``"zlg"`` | :doc:`interfaces/zlg` | ++---------------------+-------------------------------------+ diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 757cf67b4..f1d8073aa 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -31,6 +31,7 @@ The available interfaces are: interfaces/usb2can interfaces/vector interfaces/virtual + interfaces/zlg 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/zlg.rst b/doc/interfaces/zlg.rst new file mode 100644 index 000000000..47f3d65dc --- /dev/null +++ b/doc/interfaces/zlg.rst @@ -0,0 +1,76 @@ +.. _zlg: + +ZLG USB CAN +=========== + + +About +----- +Unofficial ZLG USBCAN/USBCANFD implementation for Linux + +Author: Keelung.Yang@flex.com + + +Supported devices +----------------- +Tested on USBCANFD-200U (https://www.zlg.cn/can/can/product/id/223.html) + +For other ZLG CAN devices, you can change ``bus.DeviceType`` +to another value defined ``vci.ZCAN_DEVICE`` + + +How to add new ZLG device +------------------------- +1. Add a new device type in ``vci.ZCAN_DEVICE`` +2. Update ``ZlgBitTiming.speeds`` by device type in ``timming`` module +3. Update version infomation in ``zlg/__init__.py`` + + +CAN filters +----------- +ZLG CAN Linux driver only supports filtering by ranges of CAN ID currently. +But the MCU(NXP LPC54616, in USBCANFD-200U) supports filtering by ranges and masks. +But they dropped mask filters and no plan to enable this feature again. +So hardware filtering is not supported in this interface currently. + +Also, it is possible to convert limited masks into ranges asked by official driver. +But the count of filters is limited to 64 in official driver. +Although 128 standard ID filters and 64 extended ID filters supported in MCU. + + +Note +---- +1. Before using this interface + + a. Install ``libusb`` package in your Linux distribution + b. Download ``libusbcanfd.so`` from Linux driver package + + For more infomation, please check: + https://manual.zlg.cn/web/#/188/6978 + +2. There are three official libraries which are incompatible with each other: + + a. VCI_*Functions & ZCAN_*Structures for Linux (CAN & CANFD) + b. VCI_*Functions & VCI_*Structures for Windows (CAN only) + c. ZCAN_*Functions & ZCAN_*Structures for Windows (CAN and CANFD) + + For more infomation, please check: + USBCANFD for Linux, https://manual.zlg.cn/web/#/188/6982 + + USBCANFD for Windows, https://www.zlg.cn/data/upload/software/Can/CAN_lib.rar + + USBCANFD resource, https://www.zlg.cn/can/down/down/id/223.html + +3. The Device Type IDs are different between Linux and Windows + + Linux: https://manual.zlg.cn/web/#/188/6984 + + Windows: https://manual.zlg.cn/web/#/152/6361 + +4. You can't detect ``BUS_OFF`` by Linux APIs currently, since there is + no such state in ``ZCAN_MSG_INF`` structure and the firmware will + restart automatically when ``BUS_OFF`` happened. + +5. There is also an official python-can interface implementation for Windows only. + + But declared no sustaining anymore: https://manual.zlg.cn/web/#/169/6070 diff --git a/test/test_interface_zlg.py b/test/test_interface_zlg.py new file mode 100644 index 000000000..ddf8b2f74 --- /dev/null +++ b/test/test_interface_zlg.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +import unittest +import can + +from can.exceptions import * + + +class ZlgTestCase(unittest.TestCase): + def setUp(self): + self.channel = 0 + self.device = 0 + self.std_id = 0x123 + self.ext_id = 0x123456 + self.bitrate = 500_000 + self.data_bitrate = 2_000_000 + self.bus = can.Bus( + bustype = 'zlg', + channel = self.channel, + device = self.device, + bitrate = self.bitrate, + data_bitrate = self.data_bitrate, + ) + self.can_std_msg = can.Message( + arbitration_id = self.std_id, + is_extended_id = False, + is_fd = False, + data = list(range(8)) + ) + self.can_ext_msg = can.Message( + arbitration_id = self.ext_id, + is_extended_id = True, + is_fd = False, + data = list(range(8)) + ) + self.canfd_std_msg = can.Message( + arbitration_id = self.std_id, + is_extended_id = False, + is_fd = True, + data = list(range(64)) + ) + self.canfd_ext_msg = can.Message( + arbitration_id = self.ext_id, + is_extended_id = True, + is_fd = True, + data = list(range(64)) + ) + + def tearDown(self): + self.bus.shutdown() + + def test_send_can_std(self): + try: + self.bus.send(self.can_std_msg) + except CanOperationError as ex: + self.fail('Failed to send CAN std frame') + + def test_send_can_ext(self): + try: + self.bus.send(self.can_ext_msg) + except CanOperationError as ex: + self.fail('Failed to send CAN ext frame') + + def test_send_canfd_std(self): + try: + self.bus.send(self.canfd_std_msg) + except CanOperationError as ex: + self.fail('Failed to send CANFD std frame') + + def test_send_canfd_ext(self): + try: + self.bus.send(self.canfd_ext_msg) + except CanOperationError as ex: + self.fail('Failed to send CANFD ext frame') + + def test_recv(self): + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + + +if __name__ == "__main__": + unittest.main() From 48e6299c911fea9c63624f84340b09a43418a7bf Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Tue, 18 Jan 2022 18:23:34 +0800 Subject: [PATCH 03/17] Add ZlgBitTiming.bitrates property --- can/interfaces/zlg/timing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/can/interfaces/zlg/timing.py b/can/interfaces/zlg/timing.py index 5533222de..bc7715aef 100644 --- a/can/interfaces/zlg/timing.py +++ b/can/interfaces/zlg/timing.py @@ -28,7 +28,7 @@ def __init__(self, device, **kwargs): } super().__init__(**kwargs) - def timing(self, bitrate=None, force=False): + def timing(self, bitrate=None, force=False) -> ZCAN_BIT_TIMING: if bitrate in self.speeds: return self.speeds[bitrate] elif force: @@ -40,3 +40,8 @@ def timing(self, bitrate=None, force=False): ) else: raise CanBitRateError(f'Unsupported {bitrate=}') + + @property + def bitrates(self) -> list[int]: + return self.speeds.keys() + \ No newline at end of file From 07bcfaeebe75710cef76ff59a0d8044bfd4a605a Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Mon, 24 Jan 2022 14:16:19 -0500 Subject: [PATCH 04/17] Remove ZCAN_DEVICE_TYPE / ZCAN_DEVICE_INDEX / ZCAN_CHANNEL --- can/interfaces/zlg/__init__.py | 4 +- can/interfaces/zlg/bus.py | 10 ++-- can/interfaces/zlg/vci.py | 103 +++++++++++++++------------------ 3 files changed, 55 insertions(+), 62 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index ffdce1a21..10e28420a 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,8 +2,8 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.0' -__date__ = '20220112' +__version__ = '1.0.1' +__date__ = '20220125' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' 1. Initial creation. Keelung.Yang@flex.com, 2022-01-06 diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 64825b696..2cbd2f791 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -24,9 +24,9 @@ def __init__(self, channel, device=0, tres=True, **kwargs): f'{self.__class__.__name__}{device}:{channel}@{bitrate}' if bitrate != data_bitrate: self.channel_info += f'/{data_bitrate}' - self._dev_type = ZCAN_DEVICE_TYPE(DeviceType.value) - self._dev_index = ZCAN_DEVICE_INDEX(int(device)) - self._dev_channel = ZCAN_CHANNEL(int(channel)) + self._dev_type = DeviceType.value + self._dev_index = int(device) + self._dev_channel = int(channel) self._opened = self.open(bitrate, data_bitrate) if not self._opened: raise CanInitializationError(f'Failed to open {self.channel_info}') @@ -98,7 +98,7 @@ def _to_raw(self, msg): ts = c_uint32(int(time.time())), id = msg.arbitration_id, info = info, - chn = self._dev_channel.value, + chn = self._dev_channel, len = msg.dlc ) if msg.is_fd: @@ -159,7 +159,7 @@ def send(self, msg, timeout=None) -> None: ) def open(self, bitrate, data_bitrate): - timing = ZlgBitTiming(self._dev_type.value) + timing = ZlgBitTiming(self._dev_type) if not vci_device_open(self._dev_type, self._dev_index): return False if not vci_channel_open( diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py index d0cdd5384..0fc284b5d 100644 --- a/can/interfaces/zlg/vci.py +++ b/can/interfaces/zlg/vci.py @@ -18,10 +18,6 @@ log = logging.getLogger('can.zlg') lib = cdll.LoadLibrary('libusbcanfd.so') -ZCAN_DEVICE_TYPE = c_uint32 -ZCAN_DEVICE_INDEX = c_uint32 -ZCAN_CHANNEL = c_uint32 - @unique class ZCAN_DEVICE(Enum): @@ -125,25 +121,25 @@ class ZCANFD_INIT(Structure): ] -def vci_device_open(type_, index) -> bool: - ret = lib.VCI_OpenDevice(type_, index) +def vci_device_open(dt, di) -> bool: + ret = lib.VCI_OpenDevice(c_uint32(dt), c_uint32(di)) if ret == 0: - log.error(f'Failed to open device {type_}:{index} !') + log.error(f'Failed to open device {dt}:{di} !') else: - log.info(f'Open device {type_}:{index} successfully!') + log.info(f'Open device {dt}:{di} successfully!') return ret != 0 -def vci_device_close(type_, index) -> bool: - ret = lib.VCI_CloseDevice(type_, index) +def vci_device_close(dt, di) -> bool: + ret = lib.VCI_CloseDevice(c_uint32(dt), c_uint32(di)) if ret == 0: - log.error(f'Failed to open device {type_}:{index}') + log.error(f'Failed to open device {dt}:{di}') else: - log.info(f'Open device {type_}:{index} successfully') + log.info(f'Open device {dt}:{di} successfully') return ret != 0 -def vci_channel_open(type_, index, channel, +def vci_channel_open(dt, di, ch, clock, atiming, dtiming=None, @@ -154,89 +150,86 @@ def vci_channel_open(type_, index, channel, init.clk = clock init.atim = atiming init.dtim = dtiming or atiming - ret = lib.VCI_InitCAN(type_, index, channel, byref(init)) + ret = lib.VCI_InitCAN(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(init)) if ret == 0: - log.error(f'CH{channel.value}: Initialize failed') + log.error(f'CH{ch}: Initialize failed') else: - log.info(f'CH{channel.value}: Initialized') - ret = lib.VCI_StartCAN(type_, index, channel) + log.info(f'CH{ch}: Initialized') + ret = lib.VCI_StartCAN(c_uint32(dt), c_uint32(di), c_uint32(ch)) if ret == 0: - log.error(f'CH{channel.value}: Start failed') + log.error(f'CH{ch}: Start failed') else: - log.info(f'CH{channel.value}: Started') + log.info(f'CH{ch}: Started') return ret != 0 -def vci_channel_close(type_, index, channel) -> bool: - ret = lib.VCI_ResetCAN(type_, index, channel) +def vci_channel_close(dt, di, ch) -> bool: + ret = lib.VCI_ResetCAN(c_uint32(dt), c_uint32(di), c_uint32(ch)) if ret == 0: - log.error(f'CH{channel.value}: Close failed') + log.error(f'CH{ch}: Close failed') else: - log.info(f'CH{channel.value}: Closed') + log.info(f'CH{ch}: Closed') return ret != 0 -def vci_channel_read_info(type_, index, channel, info) -> bool: - ret = lib.VCI_ReadErrInfo(type_, index, channel, byref(info)) +def vci_channel_read_info(dt, di, ch, info) -> bool: + ret = lib.VCI_ReadErrInfo(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(info)) if ret == 0: - log.error(f'CH{channel.value}: Failed to read error infomation') + log.error(f'CH{ch}: Failed to read error infomation') else: - log.info(f'CH{channel.value}: Read error infomation successfully') + log.info(f'CH{ch}: Read error infomation successfully') return ret != 0 -def vci_channel_enable_tres(type_, index, channel, value) -> bool: +def vci_channel_enable_tres(dt, di, ch, value) -> bool: tres = ZCAN_TRES(enable=ZCAN_TRES.ON if value else ZCAN_TRES.OFF) - ret = lib.VCI_SetReference(type_, index, channel, ZCAN_REF.TRES.value, byref(tres)) + ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), ZCAN_REF.TRES.value, byref(tres)) if ret == 0: - log.error(f'CH{channel.value}: Failed to {"enable" if value else "disable"} ' + log.error(f'CH{ch}: Failed to {"enable" if value else "disable"} ' f'termination resistor') else: - log.info(f'CH{channel.value}: {"Enable" if value else "Disable"} ' + log.info(f'CH{ch}: {"Enable" if value else "Disable"} ' f'termination resistor successfully') return ret != 0 -def vci_can_send(type_, index, channel, msgs, count) -> int: - ret = lib.VCI_Transmit(type_, index, channel, byref(msgs), count) +def vci_can_send(dt, di, ch, msgs, count) -> int: + ret = lib.VCI_Transmit(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count) if ret == 0: - log.error(f'CH{channel.value}: Failed to send CAN message(s)') + log.error(f'CH{ch}: Failed to send CAN message(s)') else: - log.debug(f'CH{channel.value}: Sent {len(msgs)} CAN message(s)') + log.debug(f'CH{ch}: Sent {len(msgs)} CAN message(s)') return ret -def vci_canfd_send(type_, index, channel, msgs, count) -> int: - ret = lib.VCI_TransmitFD(type_, index, channel, byref(msgs), count) +def vci_canfd_send(dt, di, ch, msgs, count) -> int: + ret = lib.VCI_TransmitFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count) if ret == 0: - log.error(f'CH{channel.value}: Failed to send CANFD message(s)') + log.error(f'CH{ch}: Failed to send CANFD message(s)') else: - log.debug(f'CH{channel.value}: Sent {len(msgs)} CANFD message(s)') + log.debug(f'CH{ch}: Sent {len(msgs)} CANFD message(s)') return ret -def vci_can_recv(type_, index, channel, msgs, count, timeout) -> int: - ret = lib.VCI_Receive(type_, index, channel, byref(msgs), count, timeout) - log.debug(f'CH{channel.value}: Received {len(msgs)} CAN message(s)') +def vci_can_recv(dt, di, ch, msgs, count, timeout) -> int: + ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count, timeout) + log.debug(f'CH{ch}: Received {len(msgs)} CAN message(s)') return ret -def vci_canfd_recv(type_, index, channel, msgs, count, timeout) -> int: - ret = lib.VCI_ReceiveFD(type_, index, channel, byref(msgs), count, timeout) - log.debug(f'CH{channel.value}: Received {len(msgs)} CANFD message(s)') +def vci_canfd_recv(dt, di, ch, msgs, count, timeout) -> int: + ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count, timeout) + log.debug(f'CH{ch}: Received {len(msgs)} CANFD message(s)') return ret -def vci_can_get_recv_num(type_, index, channel) -> int: - ret = lib.VCI_GetReceiveNum(type_, index, channel) - log.debug(f'CH{channel.value}: {ret} CAN message(s) in FIFO') +def vci_can_get_recv_num(dt, di, ch) -> int: + ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) + log.debug(f'CH{ch}: {ret} CAN message(s) in FIFO') return ret -def vci_canfd_get_recv_num(type_, index, channel) -> int: - if isinstance(channel, c_uint32): - channel = c_uint32(channel.value | 0x80000000) - else: - channel = c_uint32(int(channel) | 0x80000000) - ret = lib.VCI_GetReceiveNum(type_, index, channel) - log.debug(f'CH{channel.value&0x7FFF_FFFF}: {ret} CANFD message(s) in FIFO') +def vci_canfd_get_recv_num(dt, di, ch) -> int: + ch = ch | 0x80000000 + ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) + log.debug(f'CH{ch&0x7FFF_FFFF}: {ret} CANFD message(s) in FIFO') return ret From d7a69424904c0dcdec433b2be31b06834d72e9a2 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Wed, 26 Jan 2022 18:01:03 +0800 Subject: [PATCH 05/17] Add bus padding support --- can/interfaces/zlg/__init__.py | 7 ++++--- can/interfaces/zlg/bus.py | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index 10e28420a..d87787eb7 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,11 +2,12 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.0.1' -__date__ = '20220125' +__version__ = '1.1' +__date__ = '20220126' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' - 1. Initial creation. Keelung.Yang@flex.com, 2022-01-06 + 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 + 2. Add bus padding support, Keelung.Yang@flex.com, 2022-01-26 ''' from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 2cbd2f791..f59beccdd 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -31,6 +31,8 @@ def __init__(self, channel, device=0, tres=True, **kwargs): if not self._opened: raise CanInitializationError(f'Failed to open {self.channel_info}') self.tres = bool(tres) + self.dlc = kwargs.get('dlc', None) + self.padding = kwargs.get('padding', None) super().__init__(int(channel), **kwargs) @property @@ -84,6 +86,20 @@ def _from_raw(self, raw): ) def _to_raw(self, msg): + msg_data = bytes(msg.data) + data_size = len(msg_data) + if self.dlc and data_size < self.dlc: # Bus DLC First + padding_size = self.dlc - data_size + elif data_size < msg.dlc: # Msg DLC Second + padding_size = msg.dlc - data_size + else: + padding_size = 0 + if padding_size > 0: + if self.padding: + msg_data += bytes([self.padding[0]] * padding_size) + else: + msg_data += bytes([0] * padding_size) + data_size += padding_size info = ZCAN_MSG_INFO( txm = False, fmt = msg.is_fd, @@ -99,13 +115,13 @@ def _to_raw(self, msg): id = msg.arbitration_id, info = info, chn = self._dev_channel, - len = msg.dlc + len = data_size ) if msg.is_fd: raw = ZCAN_FD_MSG(header=header) else: raw = ZCAN_20_MSG(header=header) - ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) + ctypes.memmove(raw.dat, msg_data, data_size) return raw def _recv_internal(self, timeout): From 80285a9694aed3a4d672b3f34a7e910b24c5eeec Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Thu, 27 Jan 2022 16:46:01 +0800 Subject: [PATCH 06/17] Determine CAN-FD by data_bitrate --- can/interfaces/zlg/__init__.py | 5 +++-- can/interfaces/zlg/bus.py | 30 ++++++++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index d87787eb7..d9f0503d6 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,12 +2,13 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.1' -__date__ = '20220126' +__version__ = '1.2' +__date__ = '20220127' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 2. Add bus padding support, Keelung.Yang@flex.com, 2022-01-26 + 3. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 ''' from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index f59beccdd..c246bba66 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -18,17 +18,17 @@ def __init__(self, channel, device=0, tres=True, **kwargs): :param device: device index, [0, 1,,,] :param tres: enable/disable termination resistor on specified channel """ - bitrate = int(kwargs.get('bitrate', 500000)) - data_bitrate = int(kwargs.get('data_bitrate', bitrate)) + self.bitrate = kwargs.get('bitrate', 500000) + self.data_bitrate = kwargs.get('data_bitrate', None) self.channel_info = \ - f'{self.__class__.__name__}{device}:{channel}@{bitrate}' - if bitrate != data_bitrate: - self.channel_info += f'/{data_bitrate}' + f'{self.__class__.__name__}{device}:{channel}@{self.bitrate}' + if self.data_bitrate: + self.channel_info += f'/{self.data_bitrate}' self._dev_type = DeviceType.value self._dev_index = int(device) self._dev_channel = int(channel) - self._opened = self.open(bitrate, data_bitrate) - if not self._opened: + self.is_opened = self.open() + if not self.is_opened: raise CanInitializationError(f'Failed to open {self.channel_info}') self.tres = bool(tres) self.dlc = kwargs.get('dlc', None) @@ -152,7 +152,9 @@ def _recv_internal(self, timeout): return None, self.filters is None def send(self, msg, timeout=None) -> None: - timeout = c_uint32(int((timeout or 0)*1000)) + timeout = c_uint32(int((timeout or 0)*2000)) + if self.data_bitrate and not msg.is_fd: # Force FD if data_bitrate + msg.is_fd = True if msg.is_fd: tx_buf = (ZCAN_FD_MSG * 1)() tx_buf[0] = self._to_raw(msg) @@ -174,15 +176,19 @@ def send(self, msg, timeout=None) -> None: f'Failed to send CAN message {msg.arbitration_id:03X}!' ) - def open(self, bitrate, data_bitrate): + def open(self): timing = ZlgBitTiming(self._dev_type) + clock = timing.f_clock + bitrate = timing.timing(self.bitrate) + if self.data_bitrate: + data_bitrate = timing.timing(self.data_bitrate) + else: + data_bitrate = bitrate if not vci_device_open(self._dev_type, self._dev_index): return False if not vci_channel_open( self._dev_type, self._dev_index, self._dev_channel, - timing.f_clock, - timing.timing(bitrate), - timing.timing(data_bitrate) + clock, bitrate, data_bitrate ): vci_device_close(self._dev_type, self._dev_index) return False From 96020539529f3b5d1c0765b0ac50eace2fe3bd97 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Fri, 28 Jan 2022 15:12:36 +0800 Subject: [PATCH 07/17] Remove bus padding since it should be handled in upper layer --- can/interfaces/zlg/__init__.py | 8 ++++---- can/interfaces/zlg/bus.py | 20 ++------------------ 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index d9f0503d6..707f9f3ff 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,13 +2,13 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.2' -__date__ = '20220127' +__version__ = '1.3' +__date__ = '20220128' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 - 2. Add bus padding support, Keelung.Yang@flex.com, 2022-01-26 - 3. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 + 2. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 + 3. Remove bus padding since it should be handled in upper layer, Keelung.Yang@flex.com, 2022-01-28 ''' from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index c246bba66..83169d37d 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -31,8 +31,6 @@ def __init__(self, channel, device=0, tres=True, **kwargs): if not self.is_opened: raise CanInitializationError(f'Failed to open {self.channel_info}') self.tres = bool(tres) - self.dlc = kwargs.get('dlc', None) - self.padding = kwargs.get('padding', None) super().__init__(int(channel), **kwargs) @property @@ -86,20 +84,6 @@ def _from_raw(self, raw): ) def _to_raw(self, msg): - msg_data = bytes(msg.data) - data_size = len(msg_data) - if self.dlc and data_size < self.dlc: # Bus DLC First - padding_size = self.dlc - data_size - elif data_size < msg.dlc: # Msg DLC Second - padding_size = msg.dlc - data_size - else: - padding_size = 0 - if padding_size > 0: - if self.padding: - msg_data += bytes([self.padding[0]] * padding_size) - else: - msg_data += bytes([0] * padding_size) - data_size += padding_size info = ZCAN_MSG_INFO( txm = False, fmt = msg.is_fd, @@ -115,13 +99,13 @@ def _to_raw(self, msg): id = msg.arbitration_id, info = info, chn = self._dev_channel, - len = data_size + len = msg.dlc ) if msg.is_fd: raw = ZCAN_FD_MSG(header=header) else: raw = ZCAN_20_MSG(header=header) - ctypes.memmove(raw.dat, msg_data, data_size) + ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) return raw def _recv_internal(self, timeout): From bc6848320c729bf63778426ef0ec2b23d87fbc37 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Sat, 5 Feb 2022 23:16:06 -0500 Subject: [PATCH 08/17] Balance receiving CAN/CAN-FD messages --- can/interfaces/zlg/__init__.py | 3 ++- can/interfaces/zlg/bus.py | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index 707f9f3ff..31df2bcaf 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,13 +2,14 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.3' +__version__ = '1.4' __date__ = '20220128' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 2. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 3. Remove bus padding since it should be handled in upper layer, Keelung.Yang@flex.com, 2022-01-28 + 4. Balance receiving CAN/CAN-FD messages, Keelung.Yang@flex.com, 2022-02-06 ''' from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 83169d37d..c2fc8e065 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -110,9 +110,17 @@ def _to_raw(self, msg): def _recv_internal(self, timeout): timeout = c_uint32(int((timeout or 0)*1000)) - if vci_can_get_recv_num( + can_msg_count = vci_can_get_recv_num( self._dev_type, self._dev_index, self._dev_channel - ) > 0: + ) + canfd_msg_count = vci_canfd_get_recv_num( + self._dev_type, self._dev_index, self._dev_channel + ) + if can_msg_count > 0 and canfd_msg_count > 0: + recv_can = can_msg_count > canfd_msg_count + else: + recv_can = can_msg_count > 0 + if recv_can: rx_buf = (ZCAN_20_MSG * 1)() if vci_can_recv( self._dev_type, self._dev_index, self._dev_channel, @@ -121,9 +129,7 @@ def _recv_internal(self, timeout): return self._from_raw(rx_buf[0]), self.filters is None else: raise CanOperationError('Failed to receive message!') - if vci_canfd_get_recv_num( - self._dev_type, self._dev_index, self._dev_channel - ) > 0: + elif canfd_msg_count > 0: rx_buf = (ZCAN_FD_MSG * 1)() if vci_canfd_recv( self._dev_type, self._dev_index, self._dev_channel, From d4010573fb65b2d56c7a3f7dbfbc5939ec01d9e9 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Tue, 8 Feb 2022 11:29:17 -0500 Subject: [PATCH 09/17] Use ctypes only in vci module --- can/interfaces/zlg/bus.py | 9 +++++---- can/interfaces/zlg/vci.py | 15 ++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index c2fc8e065..e1a5f5410 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -1,7 +1,7 @@ import time import ctypes -from can import BitTiming, BusABC, BusState, Message +from can import BusABC, BusState, Message from can import CanInitializationError, CanOperationError from .vci import * @@ -27,6 +27,7 @@ def __init__(self, channel, device=0, tres=True, **kwargs): self._dev_type = DeviceType.value self._dev_index = int(device) self._dev_channel = int(channel) + self._dev_timestamp = time.time() self.is_opened = self.open() if not self.is_opened: raise CanInitializationError(f'Failed to open {self.channel_info}') @@ -95,7 +96,7 @@ def _to_raw(self, msg): # pad ) header = ZCAN_MSG_HDR( - ts = c_uint32(int(time.time())), + ts = (int(time.time() - self._dev_timestamp) * 1000) & 0xFFFF_FFFF, id = msg.arbitration_id, info = info, chn = self._dev_channel, @@ -109,7 +110,7 @@ def _to_raw(self, msg): return raw def _recv_internal(self, timeout): - timeout = c_uint32(int((timeout or 0)*1000)) + timeout = int(timeout or 0) * 1000 can_msg_count = vci_can_get_recv_num( self._dev_type, self._dev_index, self._dev_channel ) @@ -142,7 +143,7 @@ def _recv_internal(self, timeout): return None, self.filters is None def send(self, msg, timeout=None) -> None: - timeout = c_uint32(int((timeout or 0)*2000)) + timeout = int(timeout or 0) * 1000 if self.data_bitrate and not msg.is_fd: # Force FD if data_bitrate msg.is_fd = True if msg.is_fd: diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py index 0fc284b5d..65a56a559 100644 --- a/can/interfaces/zlg/vci.py +++ b/can/interfaces/zlg/vci.py @@ -44,10 +44,6 @@ class ZCAN_REF(Enum): TTX_SETTING = 0x16 # Timing send settins TTX_ENABLE = 0x17 # Timing send enable - @property - def value(self) -> c_uint32: - return c_uint32(super().value) - class ZCAN_TRES(Structure): # Termination resistor ON = 1 @@ -181,8 +177,9 @@ def vci_channel_read_info(dt, di, ch, info) -> bool: def vci_channel_enable_tres(dt, di, ch, value) -> bool: + ref = ZCAN_REF.TRES.value tres = ZCAN_TRES(enable=ZCAN_TRES.ON if value else ZCAN_TRES.OFF) - ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), ZCAN_REF.TRES.value, byref(tres)) + ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(tres)) if ret == 0: log.error(f'CH{ch}: Failed to {"enable" if value else "disable"} ' f'termination resistor') @@ -193,7 +190,7 @@ def vci_channel_enable_tres(dt, di, ch, value) -> bool: def vci_can_send(dt, di, ch, msgs, count) -> int: - ret = lib.VCI_Transmit(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count) + ret = lib.VCI_Transmit(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) if ret == 0: log.error(f'CH{ch}: Failed to send CAN message(s)') else: @@ -202,7 +199,7 @@ def vci_can_send(dt, di, ch, msgs, count) -> int: def vci_canfd_send(dt, di, ch, msgs, count) -> int: - ret = lib.VCI_TransmitFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count) + ret = lib.VCI_TransmitFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) if ret == 0: log.error(f'CH{ch}: Failed to send CANFD message(s)') else: @@ -211,13 +208,13 @@ def vci_canfd_send(dt, di, ch, msgs, count) -> int: def vci_can_recv(dt, di, ch, msgs, count, timeout) -> int: - ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count, timeout) + ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) log.debug(f'CH{ch}: Received {len(msgs)} CAN message(s)') return ret def vci_canfd_recv(dt, di, ch, msgs, count, timeout) -> int: - ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), count, timeout) + ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) log.debug(f'CH{ch}: Received {len(msgs)} CANFD message(s)') return ret From 4584868d2f4a6500b200a95f3f830eb533de9b94 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Wed, 9 Feb 2022 18:27:52 +0800 Subject: [PATCH 10/17] Implement software timeout --- can/interfaces/zlg/__init__.py | 5 +- can/interfaces/zlg/bus.py | 114 ++++++++++++++++++++------------- can/interfaces/zlg/vci.py | 33 +++++++--- doc/interfaces/zlg.rst | 15 +++++ 4 files changed, 114 insertions(+), 53 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index 31df2bcaf..10b3c7fb3 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -2,14 +2,15 @@ Unofficial ZLG USBCAN/USBCANFD implementation for Linux ''' -__version__ = '1.4' -__date__ = '20220128' +__version__ = '2.0' +__date__ = '20220209' __author__ = 'Keelung.Yang@flex.com' __history__ = ''' 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 2. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 3. Remove bus padding since it should be handled in upper layer, Keelung.Yang@flex.com, 2022-01-28 4. Balance receiving CAN/CAN-FD messages, Keelung.Yang@flex.com, 2022-02-06 + 5. Implement software timeout, Keelung.Yang@flex.com, 2022-02-09 ''' from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index e1a5f5410..4123ba7e0 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -2,7 +2,7 @@ import ctypes from can import BusABC, BusState, Message -from can import CanInitializationError, CanOperationError +from can import CanInitializationError, CanOperationError, CanTimeoutError from .vci import * from .timing import ZlgBitTiming @@ -109,8 +109,24 @@ def _to_raw(self, msg): ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) return raw - def _recv_internal(self, timeout): - timeout = int(timeout or 0) * 1000 + def _recv_one(self, fd, timeout) -> Message: + rx_buf = (ZCAN_FD_MSG * 1)() if fd else (ZCAN_20_MSG * 1)() + if fd: + ret = vci_canfd_recv( + self._dev_type, self._dev_index, self._dev_channel, + rx_buf, len(rx_buf), 100 + ) + else: + ret = vci_can_recv( + self._dev_type, self._dev_index, self._dev_channel, + rx_buf, len(rx_buf), timeout + ) + if ret > 0: + return self._from_raw(rx_buf[0]) + else: + return None + + def _recv_from_fd(self) -> bool: can_msg_count = vci_can_get_recv_num( self._dev_type, self._dev_index, self._dev_channel ) @@ -118,56 +134,68 @@ def _recv_internal(self, timeout): self._dev_type, self._dev_index, self._dev_channel ) if can_msg_count > 0 and canfd_msg_count > 0: - recv_can = can_msg_count > canfd_msg_count + return canfd_msg_count > can_msg_count + elif can_msg_count == 0 and canfd_msg_count == 0: + return bool(self.data_bitrate) else: - recv_can = can_msg_count > 0 - if recv_can: - rx_buf = (ZCAN_20_MSG * 1)() - if vci_can_recv( - self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), timeout - ) > 0: - return self._from_raw(rx_buf[0]), self.filters is None - else: - raise CanOperationError('Failed to receive message!') - elif canfd_msg_count > 0: - rx_buf = (ZCAN_FD_MSG * 1)() - if vci_canfd_recv( - self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), timeout - ) > 0: - return self._from_raw(rx_buf[0]), self.filters is None + return canfd_msg_count > 0 + + def _recv_internal(self, timeout): + while timeout is None: + if msg := self._recv_one(self._recv_from_fd(), 1000): + return msg, self.filters is None + else: + t1 = time.time() + while (time.time() - t1) <= timeout or timeout < 0.001: + # It's just delay, not aware of coming data in VCI_Receive|FD + recv_timeout = max(min(1000, int(timeout * 1000)), 10) + if msg := self._recv_one(self._recv_from_fd(), recv_timeout): + return msg, self.filters is None + elif timeout < 0.001: + return None, self.filters is None else: - raise CanOperationError('Failed to receive CANFD message!') - time.sleep(.0001) # Avoid high CPU usage if no message received - return None, self.filters is None - - def send(self, msg, timeout=None) -> None: - timeout = int(timeout or 0) * 1000 - if self.data_bitrate and not msg.is_fd: # Force FD if data_bitrate - msg.is_fd = True + raise CanTimeoutError(f'Receive timeout!') + + def _send_one(self, msg) -> int: + tx_buf = (ZCAN_FD_MSG * 1)() if msg.is_fd else (ZCAN_20_MSG * 1)() + tx_buf[0] = self._to_raw(msg) if msg.is_fd: - tx_buf = (ZCAN_FD_MSG * 1)() - tx_buf[0] = self._to_raw(msg) - if not vci_canfd_send( + return vci_canfd_send( self._dev_type, self._dev_index, self._dev_channel, tx_buf, len(tx_buf) - ): - raise CanOperationError( - f'Failed to send CANFD message {msg.arbitration_id:03X}!' - ) + ) else: - tx_buf = (ZCAN_20_MSG * 1)() - tx_buf[0] = self._to_raw(msg) - if not vci_can_send( + return vci_can_send( self._dev_type, self._dev_index, self._dev_channel, tx_buf, len(tx_buf) - ): - raise CanOperationError( - f'Failed to send CAN message {msg.arbitration_id:03X}!' + ) + + def _send_internal(self, msg, timeout=None) -> None: + while timeout is None: + if self._send_one(msg): + return + else: + t1 = time.time() + while (time.time() - t1) < timeout: + if self._send_one(msg): + return + else: + raise CanTimeoutError( + f'Send message {msg.arbitration_id:03X} timeout!' ) + + def send(self, msg, timeout=None) -> None: + # The maximum tx timeout is 4000ms, limited by firmware, as explained officially + dev_timeout = 4000 if timeout is None else 100 + vci_channel_set_tx_timout( + self._dev_type, self._dev_index, self._dev_channel, + dev_timeout + ) + if self.data_bitrate: # Force FD if data_bitrate + msg.is_fd = True + self._send_internal(msg, timeout) - def open(self): + def open(self) -> bool: timing = ZlgBitTiming(self._dev_type) clock = timing.f_clock bitrate = timing.timing(self.bitrate) diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py index 65a56a559..1ab09273b 100644 --- a/can/interfaces/zlg/vci.py +++ b/can/interfaces/zlg/vci.py @@ -53,6 +53,12 @@ class ZCAN_TRES(Structure): # Termination resistor ] +class ZCAN_TX_TIMEOUT(Structure): # Tx Timeout + _fields_=[ + ('value', c_uint32) + ] + + class ZCAN_MSG_INFO(Structure): _fields_ = [ ('txm', c_uint32, 4), # TXTYPE, 0: normal, 1: once, 2: self @@ -189,44 +195,55 @@ def vci_channel_enable_tres(dt, di, ch, value) -> bool: return ret != 0 +def vci_channel_set_tx_timout(dt, di, ch, value) -> bool: + ref = ZCAN_REF.TX_TIMEOUT.value + timeout = ZCAN_TX_TIMEOUT(value=value) + ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(timeout)) + if ret == 0: + log.error(f'CH{ch}: Failed to set tx timout {value}ms!') + else: + log.debug(f'CH{ch}: Set tx timout to {value}ms') + return ret != 0 + + def vci_can_send(dt, di, ch, msgs, count) -> int: ret = lib.VCI_Transmit(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) if ret == 0: log.error(f'CH{ch}: Failed to send CAN message(s)') else: - log.debug(f'CH{ch}: Sent {len(msgs)} CAN message(s)') + log.debug(f'CH{ch}: Sent {ret} CAN message(s)') return ret def vci_canfd_send(dt, di, ch, msgs, count) -> int: ret = lib.VCI_TransmitFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) if ret == 0: - log.error(f'CH{ch}: Failed to send CANFD message(s)') + log.error(f'CH{ch}: Failed to send CAN-FD message(s)') else: - log.debug(f'CH{ch}: Sent {len(msgs)} CANFD message(s)') + log.debug(f'CH{ch}: Sent {ret} CAN-FD message(s)') return ret def vci_can_recv(dt, di, ch, msgs, count, timeout) -> int: ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) - log.debug(f'CH{ch}: Received {len(msgs)} CAN message(s)') + log.debug(f'CH{ch}: Received {ret} CAN message(s)') return ret def vci_canfd_recv(dt, di, ch, msgs, count, timeout) -> int: ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) - log.debug(f'CH{ch}: Received {len(msgs)} CANFD message(s)') + log.debug(f'CH{ch}: Received {ret} CAN-FD message(s)') return ret def vci_can_get_recv_num(dt, di, ch) -> int: ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) - log.debug(f'CH{ch}: {ret} CAN message(s) in FIFO') + log.debug(f'CH{ch}: {ret} message(s) in CAN FIFO') return ret def vci_canfd_get_recv_num(dt, di, ch) -> int: - ch = ch | 0x80000000 + ch = ch | 0x8000_0000 ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) - log.debug(f'CH{ch&0x7FFF_FFFF}: {ret} CANFD message(s) in FIFO') + log.debug(f'CH{ch&0x7FFF_FFFF}: {ret} message(s) in CAN-FD FIFO') return ret diff --git a/doc/interfaces/zlg.rst b/doc/interfaces/zlg.rst index 47f3d65dc..d1abfde88 100644 --- a/doc/interfaces/zlg.rst +++ b/doc/interfaces/zlg.rst @@ -38,6 +38,21 @@ But the count of filters is limited to 64 in official driver. Although 128 standard ID filters and 64 extended ID filters supported in MCU. +Tx/Rx timeout +------------- +You've to set tx timeout by VCI_SetReference(), since no timeout argument in +VCI_Transmit|FD(). And the maximum tx timeout is 4000ms, +limited by firmware, as explained officially. + +And rx timeout is just delay, not aware of coming data in VCI_Receive|FD() API. +E.g. if rx_timeout is 2s and received data in 1s, you got data after 2s. + +In this interface implementation, to avoid above Tx/Rx limitations, +both tx and rx timeout is implemented by software. +So you can set timeout to ``None`` in send() or recv() to wait indefinitely. +Or other values to wait for an actual timeout. + + Note ---- 1. Before using this interface From 9c6a6152bb6bb66f0e55bf9ee98a0a9438a2cbc8 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Wed, 9 Feb 2022 19:38:28 +0800 Subject: [PATCH 11/17] Raise CanInitializationError if not in Linux --- can/interfaces/zlg/bus.py | 3 +++ can/interfaces/zlg/timing.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 4123ba7e0..1a82954b1 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -1,5 +1,6 @@ import time import ctypes +import platform from can import BusABC, BusState, Message from can import CanInitializationError, CanOperationError, CanTimeoutError @@ -18,6 +19,8 @@ def __init__(self, channel, device=0, tres=True, **kwargs): :param device: device index, [0, 1,,,] :param tres: enable/disable termination resistor on specified channel """ + if platform.system() != "Linux": + raise CanInitializationError(f'Only Linux is supported currently') self.bitrate = kwargs.get('bitrate', 500000) self.data_bitrate = kwargs.get('data_bitrate', None) self.channel_info = \ diff --git a/can/interfaces/zlg/timing.py b/can/interfaces/zlg/timing.py index bc7715aef..97741c539 100644 --- a/can/interfaces/zlg/timing.py +++ b/can/interfaces/zlg/timing.py @@ -1,6 +1,3 @@ -import time -import ctypes - from can import BitTiming, CanInitializationError, CanBitRateError from .vci import ZCAN_BIT_TIMING, ZCAN_DEVICE From d38ab5d4e293a9e41ac13bda07a69e6adc08abee Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Thu, 10 Feb 2022 14:21:27 +0800 Subject: [PATCH 12/17] Improve accuracy of software timeout --- can/interfaces/zlg/bus.py | 71 +++++++++++++++------------- can/interfaces/zlg/vci.py | 10 ++-- test/test_interface_zlg.py | 95 +++++++++++++++++++++++++++++++++++--- 3 files changed, 134 insertions(+), 42 deletions(-) diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 1a82954b1..18f7462a6 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -112,50 +112,57 @@ def _to_raw(self, msg): ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) return raw - def _recv_one(self, fd, timeout) -> Message: + def _available_msgs(self) -> tuple[bool, int]: + '''Return (is_fd, buffered_msg_count)''' + can_msg_count = vci_can_get_recv_num( + self._dev_type, self._dev_index, self._dev_channel + ) + canfd_msg_count = vci_canfd_get_recv_num( + self._dev_type, self._dev_index, self._dev_channel + ) + if can_msg_count > 0 and canfd_msg_count > 0: + if canfd_msg_count > can_msg_count: + return True, canfd_msg_count + else: + return False, can_msg_count + elif can_msg_count == 0 and canfd_msg_count == 0: + return bool(self.data_bitrate), 0 + else: + return canfd_msg_count > 0, canfd_msg_count or can_msg_count + + def _recv_one(self, fd) -> Message: + delay = 1 # ZLG cann't comfirm what's happen if delay == 0 rx_buf = (ZCAN_FD_MSG * 1)() if fd else (ZCAN_20_MSG * 1)() if fd: ret = vci_canfd_recv( self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), 100 + rx_buf, len(rx_buf), delay ) else: ret = vci_can_recv( self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), timeout + rx_buf, len(rx_buf), delay ) if ret > 0: return self._from_raw(rx_buf[0]) else: return None - def _recv_from_fd(self) -> bool: - can_msg_count = vci_can_get_recv_num( - self._dev_type, self._dev_index, self._dev_channel - ) - canfd_msg_count = vci_canfd_get_recv_num( - self._dev_type, self._dev_index, self._dev_channel - ) - if can_msg_count > 0 and canfd_msg_count > 0: - return canfd_msg_count > can_msg_count - elif can_msg_count == 0 and canfd_msg_count == 0: - return bool(self.data_bitrate) - else: - return canfd_msg_count > 0 - def _recv_internal(self, timeout): - while timeout is None: - if msg := self._recv_one(self._recv_from_fd(), 1000): - return msg, self.filters is None - else: - t1 = time.time() - while (time.time() - t1) <= timeout or timeout < 0.001: - # It's just delay, not aware of coming data in VCI_Receive|FD - recv_timeout = max(min(1000, int(timeout * 1000)), 10) - if msg := self._recv_one(self._recv_from_fd(), recv_timeout): + t1 = time.time() + while True: + is_fd, msg_count = self._available_msgs() + if msg_count: + if msg := self._recv_one(is_fd): return msg, self.filters is None - elif timeout < 0.001: - return None, self.filters is None + else: + raise CanOperationError(f'Failed to receive!') + elif timeout is None: + time.sleep(0.001) + elif timeout < 0.001: + return None, self.filters is None + elif (time.time() - t1) < timeout: + time.sleep(0.001) else: raise CanTimeoutError(f'Receive timeout!') @@ -179,9 +186,11 @@ def _send_internal(self, msg, timeout=None) -> None: return else: t1 = time.time() - while (time.time() - t1) < timeout: + while (time.time() - t1) < timeout or timeout < 0.001: if self._send_one(msg): return + elif timeout < 0.001: + return else: raise CanTimeoutError( f'Send message {msg.arbitration_id:03X} timeout!' @@ -189,8 +198,8 @@ def _send_internal(self, msg, timeout=None) -> None: def send(self, msg, timeout=None) -> None: # The maximum tx timeout is 4000ms, limited by firmware, as explained officially - dev_timeout = 4000 if timeout is None else 100 - vci_channel_set_tx_timout( + dev_timeout = 4000 if timeout is None else 10 + vci_channel_set_tx_timeout( self._dev_type, self._dev_index, self._dev_channel, dev_timeout ) diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py index 1ab09273b..d9891428e 100644 --- a/can/interfaces/zlg/vci.py +++ b/can/interfaces/zlg/vci.py @@ -195,7 +195,7 @@ def vci_channel_enable_tres(dt, di, ch, value) -> bool: return ret != 0 -def vci_channel_set_tx_timout(dt, di, ch, value) -> bool: +def vci_channel_set_tx_timeout(dt, di, ch, value) -> bool: ref = ZCAN_REF.TX_TIMEOUT.value timeout = ZCAN_TX_TIMEOUT(value=value) ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(timeout)) @@ -224,14 +224,14 @@ def vci_canfd_send(dt, di, ch, msgs, count) -> int: return ret -def vci_can_recv(dt, di, ch, msgs, count, timeout) -> int: - ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) +def vci_can_recv(dt, di, ch, msgs, count, delay) -> int: + ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(delay)) log.debug(f'CH{ch}: Received {ret} CAN message(s)') return ret -def vci_canfd_recv(dt, di, ch, msgs, count, timeout) -> int: - ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(timeout)) +def vci_canfd_recv(dt, di, ch, msgs, count, delay) -> int: + ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(delay)) log.debug(f'CH{ch}: Received {ret} CAN-FD message(s)') return ret diff --git a/test/test_interface_zlg.py b/test/test_interface_zlg.py index ddf8b2f74..f0b99b1ab 100644 --- a/test/test_interface_zlg.py +++ b/test/test_interface_zlg.py @@ -2,6 +2,7 @@ # coding: utf-8 import unittest +import time import can from can.exceptions import * @@ -20,7 +21,7 @@ def setUp(self): channel = self.channel, device = self.device, bitrate = self.bitrate, - data_bitrate = self.data_bitrate, + data_bitrate = self.data_bitrate ) self.can_std_msg = can.Message( arbitration_id = self.std_id, @@ -50,34 +51,116 @@ def setUp(self): def tearDown(self): self.bus.shutdown() - def test_send_can_std(self): + def testSendStdMsg(self): try: self.bus.send(self.can_std_msg) except CanOperationError as ex: self.fail('Failed to send CAN std frame') - def test_send_can_ext(self): + def testSendExtMsg(self): try: self.bus.send(self.can_ext_msg) except CanOperationError as ex: self.fail('Failed to send CAN ext frame') - def test_send_canfd_std(self): + def testSendStdMsgFD(self): try: self.bus.send(self.canfd_std_msg) except CanOperationError as ex: self.fail('Failed to send CANFD std frame') - def test_send_canfd_ext(self): + def testSendExtMsgFD(self): try: self.bus.send(self.canfd_ext_msg) except CanOperationError as ex: self.fail('Failed to send CANFD ext frame') - def test_recv(self): + def testSendTimeout0S(self): + try: + timeout = 0 + print(f'Set send {timeout=}') + t1 = time.time() + self.bus.send(self.can_std_msg, timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=10) + except Exception as ex: + self.fail(str(ex)) + + def testSendTimeout1S(self): + try: + timeout = 1 + print(f'Set send {timeout=}') + t1 = time.time() + self.bus.send(self.can_std_msg, timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + except Exception as ex: + self.fail(str(ex)) + + def testSendTimeout5S(self): + try: + timeout = 5 + print(f'Set send {timeout=}') + t1 = time.time() + self.bus.send(self.can_std_msg, timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + except Exception as ex: + self.fail(str(ex)) + + def testSendTimeoutForever(self): + try: + timeout = None + print(f'Set send {timeout=}') + self.bus.send(self.can_std_msg, timeout) + except Exception as ex: + self.fail(str(ex)) + + def testRecv(self): msg = self.bus.recv(0) self.assertIsNotNone(msg) + def testRecvTimeout0S(self): + try: + timeout = 0 + print(f'Set receive {timeout=}') + t1 = time.time() + msg = self.bus.recv(timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=10) + except Exception as ex: + self.fail(str(ex)) + + def testRecvTimeout1S(self): + try: + timeout = 1 + print(f'Set receive {timeout=}') + t1 = time.time() + msg = self.bus.recv(timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + except Exception as ex: + self.fail(str(ex)) + + def testRecvTimeout5S(self): + try: + timeout = 5 + print(f'Set receive {timeout=}') + t1 = time.time() + msg = self.bus.recv(timeout) + t2 = time.time() + self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + except Exception as ex: + self.fail(str(ex)) + + def testRecvTimeoutForever(self): + try: + timeout = None + print(f'Set receive {timeout=}') + msg = self.bus.recv(timeout) + except Exception as ex: + self.fail(str(ex)) + if __name__ == "__main__": unittest.main() From 652a96bffe1d204e2fc592c8a1819178d1dc7fb6 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Fri, 23 Sep 2022 18:12:36 +0800 Subject: [PATCH 13/17] Add build-in 5Mbps support --- can/interfaces/zlg/timing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/zlg/timing.py b/can/interfaces/zlg/timing.py index 97741c539..a9777ad63 100644 --- a/can/interfaces/zlg/timing.py +++ b/can/interfaces/zlg/timing.py @@ -22,6 +22,7 @@ def __init__(self, device, **kwargs): 1_000_000: ZCAN_BIT_TIMING(tseg1=46, tseg2=11, sjw=3, brp=0), 2_000_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=1), 4_000_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=0), + 5_000_000: ZCAN_BIT_TIMING(tseg1=7, tseg2=2, sjw=2, brp=0), } super().__init__(**kwargs) From d1db8e02dfc2218c0142b1597b90be35334440aa Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Mon, 24 Apr 2023 11:29:47 +0800 Subject: [PATCH 14/17] Fix conflicts while merging v4.2 --- can/__init__.py | 135 ++++++++++++++++++++++++++++++++---------- can/exceptions.py | 30 ++++++---- doc/configuration.rst | 46 ++++++++++---- doc/interfaces.rst | 34 ++++------- 4 files changed, 169 insertions(+), 76 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 0897551bf..e8d7850ca 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -6,47 +6,122 @@ """ import logging -from typing import Dict, Any +from typing import Any, Dict -__version__ = "4.0.0" +__version__ = "4.2.0rc0" +__all__ = [ + "ASCReader", + "ASCWriter", + "AsyncBufferedReader", + "BitTiming", + "BitTimingFd", + "BLFReader", + "BLFWriter", + "BufferedReader", + "Bus", + "BusABC", + "BusState", + "CanError", + "CanInitializationError", + "CanInterfaceNotImplementedError", + "CanOperationError", + "CanTimeoutError", + "CanutilsLogReader", + "CanutilsLogWriter", + "CSVReader", + "CSVWriter", + "CyclicSendTaskABC", + "LimitedDurationCyclicSendTaskABC", + "Listener", + "Logger", + "LogReader", + "ModifiableCyclicTaskABC", + "Message", + "MessageSync", + "MF4Reader", + "MF4Writer", + "Notifier", + "Printer", + "RedirectReader", + "RestartableCyclicTaskABC", + "SizedRotatingLogger", + "SqliteReader", + "SqliteWriter", + "ThreadSafeBus", + "TRCFileVersion", + "TRCReader", + "TRCWriter", + "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") 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, - CanBitRateError, ) - -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 - -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, - ModifiableCyclicTaskABC, - MultiRateCyclicSendTaskABC, - RestartableCyclicTaskABC, +from .interfaces import VALID_INTERFACES +from .io import ( + ASCReader, + ASCWriter, + BLFReader, + BLFWriter, + CanutilsLogReader, + CanutilsLogWriter, + CSVReader, + CSVWriter, + Logger, + LogReader, + MessageSync, + MF4Reader, + MF4Writer, + 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/exceptions.py b/can/exceptions.py index 6e2aecbf5..8d5fbab8f 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -18,9 +18,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 @@ -33,15 +31,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 @@ -75,7 +79,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/doc/configuration.rst b/doc/configuration.rst index 8c959179b..be81a1131 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -1,3 +1,5 @@ +.. _configuration: + Configuration ============= @@ -28,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 @@ -100,6 +102,7 @@ For example: ``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}`` +.. _interface names: Interface Names --------------- @@ -109,34 +112,53 @@ 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` | +---------------------+-------------------------------------+ | ``"zlg"`` | :doc:`interfaces/zlg` | +---------------------+-------------------------------------+ +| ``"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/interfaces.rst b/doc/interfaces.rst index f1d8073aa..dd3046e51 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -1,22 +1,30 @@ -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 following hardware interfaces are included in python-can: .. toctree:: :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 @@ -26,26 +34,10 @@ The available interfaces are: interfaces/serial interfaces/slcan interfaces/socketcan + interfaces/socketcand interfaces/systec - interfaces/udp_multicast interfaces/usb2can interfaces/vector - interfaces/virtual interfaces/zlg -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 format of the entry point is ``interface_name=module:classname`` where -``classname`` is a concrete :class:`can.BusABC` implementation. - -:: - - entry_points={ - 'can.interface': [ - "interface_name=module:classname", - ] - }, - - -The *Interface Names* are listed in :doc:`configuration`. +Additional interface types can be added via the :ref:`plugin interface`, or by installing a third party package that utilises the :ref:`plugin interface`. From 31631cf69381d5750cc1d1474ab003f3dd25c767 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Mon, 24 Apr 2023 13:12:26 +0800 Subject: [PATCH 15/17] Discard changes to avoid conflicts --- can/exceptions.py | 262 ++++++++++++++++++++---------------------- doc/configuration.rst | 2 - doc/interfaces.rst | 2 +- 3 files changed, 127 insertions(+), 139 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 8d5fbab8f..e1c970e27 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,136 +1,126 @@ -""" -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 - +-- CanBitRateError - -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, 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: - - .. 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 - - :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 - """ - - -class CanBitRateError(CanError): - """Indicates the invalid / unsupported bitrate. - - Example scenarios: - - Invalid / unsupported bitrate on bus - - Can't convert between bit timing and bitrate - """ - - -@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, 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: + + .. 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 + + :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/doc/configuration.rst b/doc/configuration.rst index be81a1131..494351350 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -156,8 +156,6 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"vector"`` | :doc:`interfaces/vector` | +---------------------+-------------------------------------+ -| ``"zlg"`` | :doc:`interfaces/zlg` | -+---------------------+-------------------------------------+ | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ diff --git a/doc/interfaces.rst b/doc/interfaces.rst index dd3046e51..6645ec338 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -38,6 +38,6 @@ The following hardware interfaces are included in python-can: interfaces/systec interfaces/usb2can interfaces/vector - interfaces/zlg + Additional interface types can be added via the :ref:`plugin interface`, or by installing a third party package that utilises the :ref:`plugin interface`. From 471d7fc8776e4756756f511fba1984e649cbcf4d Mon Sep 17 00:00:00 2001 From: keelung-yang Date: Mon, 24 Apr 2023 05:14:21 +0000 Subject: [PATCH 16/17] Format code with black --- can/interfaces/zlg/__init__.py | 14 +- can/interfaces/zlg/bus.py | 130 +++++++++--------- can/interfaces/zlg/timing.py | 17 ++- can/interfaces/zlg/vci.py | 234 +++++++++++++++++---------------- test/test_interface_zlg.py | 74 +++++------ 5 files changed, 243 insertions(+), 226 deletions(-) diff --git a/can/interfaces/zlg/__init__.py b/can/interfaces/zlg/__init__.py index 10b3c7fb3..68202f2ca 100644 --- a/can/interfaces/zlg/__init__.py +++ b/can/interfaces/zlg/__init__.py @@ -1,16 +1,16 @@ -''' +""" Unofficial ZLG USBCAN/USBCANFD implementation for Linux -''' +""" -__version__ = '2.0' -__date__ = '20220209' -__author__ = 'Keelung.Yang@flex.com' -__history__ = ''' +__version__ = "2.0" +__date__ = "20220209" +__author__ = "Keelung.Yang@flex.com" +__history__ = """ 1. Initial creation, Keelung.Yang@flex.com, 2022-01-06 2. Determine CAN-FD by data_bitrate, Keelung.Yang@flex.com, 2022-01-27 3. Remove bus padding since it should be handled in upper layer, Keelung.Yang@flex.com, 2022-01-28 4. Balance receiving CAN/CAN-FD messages, Keelung.Yang@flex.com, 2022-02-06 5. Implement software timeout, Keelung.Yang@flex.com, 2022-02-09 -''' +""" from can.interfaces.zlg.bus import ZlgCanBus diff --git a/can/interfaces/zlg/bus.py b/can/interfaces/zlg/bus.py index 18f7462a6..7d958bbda 100644 --- a/can/interfaces/zlg/bus.py +++ b/can/interfaces/zlg/bus.py @@ -20,28 +20,29 @@ def __init__(self, channel, device=0, tres=True, **kwargs): :param tres: enable/disable termination resistor on specified channel """ if platform.system() != "Linux": - raise CanInitializationError(f'Only Linux is supported currently') - self.bitrate = kwargs.get('bitrate', 500000) - self.data_bitrate = kwargs.get('data_bitrate', None) - self.channel_info = \ - f'{self.__class__.__name__}{device}:{channel}@{self.bitrate}' + raise CanInitializationError(f"Only Linux is supported currently") + self.bitrate = kwargs.get("bitrate", 500000) + self.data_bitrate = kwargs.get("data_bitrate", None) + self.channel_info = ( + f"{self.__class__.__name__}{device}:{channel}@{self.bitrate}" + ) if self.data_bitrate: - self.channel_info += f'/{self.data_bitrate}' + self.channel_info += f"/{self.data_bitrate}" self._dev_type = DeviceType.value self._dev_index = int(device) self._dev_channel = int(channel) self._dev_timestamp = time.time() self.is_opened = self.open() if not self.is_opened: - raise CanInitializationError(f'Failed to open {self.channel_info}') + raise CanInitializationError(f"Failed to open {self.channel_info}") self.tres = bool(tres) super().__init__(int(channel), **kwargs) @property def tres(self): - '''Termination resistor''' + """Termination resistor""" return self._tres - + @tres.setter def tres(self, value): self._tres = bool(value) @@ -50,60 +51,58 @@ def tres(self, value): ): raise CanOperationError( f'Failed to {"enable" if value else "disable"} ' - f'termination resistor for {self.channel_info} !' + f"termination resistor for {self.channel_info} !" ) - + @property def state(self): err_msg = ZCAN_ERR_MSG() if not vci_channel_read_info( self._dev_type, self._dev_index, self._dev_channel, err_msg ): - raise CanOperationError( - f'Failed to read CAN{self._dev_channel} status!' - ) + raise CanOperationError(f"Failed to read CAN{self._dev_channel} status!") if err_msg.info.err: if err_msg.info.est: return BusState.ERROR else: return BusState.ACTIVE - return None # https://github.com/hardbyte/python-can/issues/736 - + return None # https://github.com/hardbyte/python-can/issues/736 + @state.setter def state(self, value): raise NotImplementedError() def _from_raw(self, raw): return Message( - timestamp = raw.header.ts, - arbitration_id = raw.header.id, - is_fd = bool(raw.header.info.fmt), - is_extended_id = bool(raw.header.info.sef), - is_remote_frame = bool(raw.header.info.sdf), - is_error_frame = bool(raw.header.info.err), - bitrate_switch = bool(raw.header.info.brs), - dlc = raw.header.len, - data = bytes(raw.dat[:raw.header.len]), - channel = raw.header.chn, + timestamp=raw.header.ts, + arbitration_id=raw.header.id, + is_fd=bool(raw.header.info.fmt), + is_extended_id=bool(raw.header.info.sef), + is_remote_frame=bool(raw.header.info.sdf), + is_error_frame=bool(raw.header.info.err), + bitrate_switch=bool(raw.header.info.brs), + dlc=raw.header.len, + data=bytes(raw.dat[: raw.header.len]), + channel=raw.header.chn, ) - + def _to_raw(self, msg): info = ZCAN_MSG_INFO( - txm = False, - fmt = msg.is_fd, - sdf = msg.is_remote_frame, - sef = msg.is_extended_id, - err = msg.is_error_frame, - brs = msg.bitrate_switch, - est = msg.error_state_indicator, + txm=False, + fmt=msg.is_fd, + sdf=msg.is_remote_frame, + sef=msg.is_extended_id, + err=msg.is_error_frame, + brs=msg.bitrate_switch, + est=msg.error_state_indicator, # pad ) header = ZCAN_MSG_HDR( - ts = (int(time.time() - self._dev_timestamp) * 1000) & 0xFFFF_FFFF, - id = msg.arbitration_id, - info = info, - chn = self._dev_channel, - len = msg.dlc + ts=(int(time.time() - self._dev_timestamp) * 1000) & 0xFFFF_FFFF, + id=msg.arbitration_id, + info=info, + chn=self._dev_channel, + len=msg.dlc, ) if msg.is_fd: raw = ZCAN_FD_MSG(header=header) @@ -111,9 +110,9 @@ def _to_raw(self, msg): raw = ZCAN_20_MSG(header=header) ctypes.memmove(raw.dat, bytes(msg.data), msg.dlc) return raw - + def _available_msgs(self) -> tuple[bool, int]: - '''Return (is_fd, buffered_msg_count)''' + """Return (is_fd, buffered_msg_count)""" can_msg_count = vci_can_get_recv_num( self._dev_type, self._dev_index, self._dev_channel ) @@ -135,13 +134,21 @@ def _recv_one(self, fd) -> Message: rx_buf = (ZCAN_FD_MSG * 1)() if fd else (ZCAN_20_MSG * 1)() if fd: ret = vci_canfd_recv( - self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), delay + self._dev_type, + self._dev_index, + self._dev_channel, + rx_buf, + len(rx_buf), + delay, ) else: ret = vci_can_recv( - self._dev_type, self._dev_index, self._dev_channel, - rx_buf, len(rx_buf), delay + self._dev_type, + self._dev_index, + self._dev_channel, + rx_buf, + len(rx_buf), + delay, ) if ret > 0: return self._from_raw(rx_buf[0]) @@ -156,7 +163,7 @@ def _recv_internal(self, timeout): if msg := self._recv_one(is_fd): return msg, self.filters is None else: - raise CanOperationError(f'Failed to receive!') + raise CanOperationError(f"Failed to receive!") elif timeout is None: time.sleep(0.001) elif timeout < 0.001: @@ -164,22 +171,20 @@ def _recv_internal(self, timeout): elif (time.time() - t1) < timeout: time.sleep(0.001) else: - raise CanTimeoutError(f'Receive timeout!') - + raise CanTimeoutError(f"Receive timeout!") + def _send_one(self, msg) -> int: tx_buf = (ZCAN_FD_MSG * 1)() if msg.is_fd else (ZCAN_20_MSG * 1)() tx_buf[0] = self._to_raw(msg) if msg.is_fd: return vci_canfd_send( - self._dev_type, self._dev_index, self._dev_channel, - tx_buf, len(tx_buf) + self._dev_type, self._dev_index, self._dev_channel, tx_buf, len(tx_buf) ) else: return vci_can_send( - self._dev_type, self._dev_index, self._dev_channel, - tx_buf, len(tx_buf) + self._dev_type, self._dev_index, self._dev_channel, tx_buf, len(tx_buf) ) - + def _send_internal(self, msg, timeout=None) -> None: while timeout is None: if self._send_one(msg): @@ -192,18 +197,15 @@ def _send_internal(self, msg, timeout=None) -> None: elif timeout < 0.001: return else: - raise CanTimeoutError( - f'Send message {msg.arbitration_id:03X} timeout!' - ) - + raise CanTimeoutError(f"Send message {msg.arbitration_id:03X} timeout!") + def send(self, msg, timeout=None) -> None: # The maximum tx timeout is 4000ms, limited by firmware, as explained officially dev_timeout = 4000 if timeout is None else 10 vci_channel_set_tx_timeout( - self._dev_type, self._dev_index, self._dev_channel, - dev_timeout + self._dev_type, self._dev_index, self._dev_channel, dev_timeout ) - if self.data_bitrate: # Force FD if data_bitrate + if self.data_bitrate: # Force FD if data_bitrate msg.is_fd = True self._send_internal(msg, timeout) @@ -218,8 +220,12 @@ def open(self) -> bool: if not vci_device_open(self._dev_type, self._dev_index): return False if not vci_channel_open( - self._dev_type, self._dev_index, self._dev_channel, - clock, bitrate, data_bitrate + self._dev_type, + self._dev_index, + self._dev_channel, + clock, + bitrate, + data_bitrate, ): vci_device_close(self._dev_type, self._dev_index) return False diff --git a/can/interfaces/zlg/timing.py b/can/interfaces/zlg/timing.py index a9777ad63..118351b26 100644 --- a/can/interfaces/zlg/timing.py +++ b/can/interfaces/zlg/timing.py @@ -9,12 +9,12 @@ def __init__(self, device, **kwargs): try: self._device = ZCAN_DEVICE(device) except: - raise CanInitializationError(f'Unsupported ZLG CAN device {device} !') + raise CanInitializationError(f"Unsupported ZLG CAN device {device} !") if self._device in ( ZCAN_DEVICE.USBCAN, ZCAN_DEVICE.USBCANFD_200U, ): - kwargs.setdefault('f_clock', 60000000) + kwargs.setdefault("f_clock", 60000000) self.speeds = { 125_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=31), 250_000: ZCAN_BIT_TIMING(tseg1=10, tseg2=2, sjw=2, brp=15), @@ -25,21 +25,20 @@ def __init__(self, device, **kwargs): 5_000_000: ZCAN_BIT_TIMING(tseg1=7, tseg2=2, sjw=2, brp=0), } super().__init__(**kwargs) - + def timing(self, bitrate=None, force=False) -> ZCAN_BIT_TIMING: if bitrate in self.speeds: return self.speeds[bitrate] elif force: return ZCAN_BIT_TIMING( - tseg1 = self.tseg1, - tseg2 = self.tseg2, - sjw = self.sjw, - brp = self.brp, + tseg1=self.tseg1, + tseg2=self.tseg2, + sjw=self.sjw, + brp=self.brp, ) else: - raise CanBitRateError(f'Unsupported {bitrate=}') + raise CanBitRateError(f"Unsupported {bitrate=}") @property def bitrates(self) -> list[int]: return self.speeds.keys() - \ No newline at end of file diff --git a/can/interfaces/zlg/vci.py b/can/interfaces/zlg/vci.py index d9891428e..3dce4768b 100644 --- a/can/interfaces/zlg/vci.py +++ b/can/interfaces/zlg/vci.py @@ -1,9 +1,9 @@ -''' +""" Wrap libusbcanfd.so -''' +""" ####################################################################### -# For consistency, use the coding style and definitions +# For consistency, use the coding style and definitions # as similar as USBCANFD_DEMO.py in Linux driver package # https://manual.zlg.cn/web/#/146 ####################################################################### @@ -11,239 +11,251 @@ import logging from enum import unique, auto, Enum, Flag -from ctypes import c_ubyte, c_uint8, c_uint16, c_uint32 +from ctypes import c_ubyte, c_uint8, c_uint16, c_uint32 from ctypes import cdll, byref, Structure -log = logging.getLogger('can.zlg') -lib = cdll.LoadLibrary('libusbcanfd.so') +log = logging.getLogger("can.zlg") +lib = cdll.LoadLibrary("libusbcanfd.so") @unique class ZCAN_DEVICE(Enum): - '''CAN Device Type - Only USBCAN / USBCANFD_200U supported now - For more infomation, please check - https://manual.zlg.cn/web/#/188/6984 - ''' - USBCAN = 4 - USBCANFD_200U = 33 + """CAN Device Type + Only USBCAN / USBCANFD_200U supported now + For more infomation, please check + https://manual.zlg.cn/web/#/188/6984 + """ + + USBCAN = 4 + USBCANFD_200U = 33 class ZCAN_MODE(Flag): - LISTEN_ONLY = auto() # 0: Normal, 1: Listen only - BOSCH = auto() # 0: ISO, 1: BOSCH - NORMAL = (~LISTEN_ONLY & ~BOSCH) & 0x03 + LISTEN_ONLY = auto() # 0: Normal, 1: Listen only + BOSCH = auto() # 0: ISO, 1: BOSCH + NORMAL = (~LISTEN_ONLY & ~BOSCH) & 0x03 @unique class ZCAN_REF(Enum): - FILTER = 0x14 # CAN Message Filter - TRES = 0x18 # Termination resistor - TX_TIMEOUT = 0x44 # Send timeout in ms - TTX_SETTING = 0x16 # Timing send settins - TTX_ENABLE = 0x17 # Timing send enable + FILTER = 0x14 # CAN Message Filter + TRES = 0x18 # Termination resistor + TX_TIMEOUT = 0x44 # Send timeout in ms + TTX_SETTING = 0x16 # Timing send settins + TTX_ENABLE = 0x17 # Timing send enable -class ZCAN_TRES(Structure): # Termination resistor - ON = 1 +class ZCAN_TRES(Structure): # Termination resistor + ON = 1 OFF = 0 - _fields_=[ - ('enable', c_uint8) - ] + _fields_ = [("enable", c_uint8)] -class ZCAN_TX_TIMEOUT(Structure): # Tx Timeout - _fields_=[ - ('value', c_uint32) - ] +class ZCAN_TX_TIMEOUT(Structure): # Tx Timeout + _fields_ = [("value", c_uint32)] class ZCAN_MSG_INFO(Structure): _fields_ = [ - ('txm', c_uint32, 4), # TXTYPE, 0: normal, 1: once, 2: self - ('fmt', c_uint32, 4), # 0:CAN2.0, 1:CANFD - ('sdf', c_uint32, 1), # 0: data frame, 1: remote frame - ('sef', c_uint32, 1), # 0: std frame, 1: ext frame - ('err', c_uint32, 1), # error flag - ('brs', c_uint32, 1), # bit-rate switch, 0: Not speed up, 1: speed up - ('est', c_uint32, 1), # error state - ('pad', c_uint32, 19) + ("txm", c_uint32, 4), # TXTYPE, 0: normal, 1: once, 2: self + ("fmt", c_uint32, 4), # 0:CAN2.0, 1:CANFD + ("sdf", c_uint32, 1), # 0: data frame, 1: remote frame + ("sef", c_uint32, 1), # 0: std frame, 1: ext frame + ("err", c_uint32, 1), # error flag + ("brs", c_uint32, 1), # bit-rate switch, 0: Not speed up, 1: speed up + ("est", c_uint32, 1), # error state + ("pad", c_uint32, 19), ] -class ZCAN_MSG_HDR(Structure): +class ZCAN_MSG_HDR(Structure): _fields_ = [ - ('ts', c_uint32), # timestamp - ('id', c_uint32), # can-id - ('info', ZCAN_MSG_INFO), - ('pad', c_uint16), - ('chn', c_uint8), # channel - ('len', c_uint8) # data length + ("ts", c_uint32), # timestamp + ("id", c_uint32), # can-id + ("info", ZCAN_MSG_INFO), + ("pad", c_uint16), + ("chn", c_uint8), # channel + ("len", c_uint8), # data length ] -class ZCAN_20_MSG(Structure): - _fields_ = [ - ('header', ZCAN_MSG_HDR), - ('dat', c_ubyte*8) - ] +class ZCAN_20_MSG(Structure): + _fields_ = [("header", ZCAN_MSG_HDR), ("dat", c_ubyte * 8)] -class ZCAN_FD_MSG(Structure): - _fields_ = [ - ('header', ZCAN_MSG_HDR), - ('dat', c_ubyte*64) - ] +class ZCAN_FD_MSG(Structure): + _fields_ = [("header", ZCAN_MSG_HDR), ("dat", c_ubyte * 64)] -class ZCAN_ERR_MSG(Structure): - _fields_ = [ - ('header', ZCAN_MSG_HDR), - ('dat', c_ubyte*8) - ] +class ZCAN_ERR_MSG(Structure): + _fields_ = [("header", ZCAN_MSG_HDR), ("dat", c_ubyte * 8)] class ZCAN_BIT_TIMING(Structure): _fields_ = [ - ('tseg1', c_uint8), - ('tseg2', c_uint8), - ('sjw', c_uint8), - ('smp', c_uint8), # Not used - ('brp', c_uint16) + ("tseg1", c_uint8), + ("tseg2", c_uint8), + ("sjw", c_uint8), + ("smp", c_uint8), # Not used + ("brp", c_uint16), ] class ZCANFD_INIT(Structure): _fields_ = [ - ('clk', c_uint32), - ('mode', c_uint32), - ('atim', ZCAN_BIT_TIMING), - ('dtim', ZCAN_BIT_TIMING) + ("clk", c_uint32), + ("mode", c_uint32), + ("atim", ZCAN_BIT_TIMING), + ("dtim", ZCAN_BIT_TIMING), ] def vci_device_open(dt, di) -> bool: ret = lib.VCI_OpenDevice(c_uint32(dt), c_uint32(di)) if ret == 0: - log.error(f'Failed to open device {dt}:{di} !') + log.error(f"Failed to open device {dt}:{di} !") else: - log.info(f'Open device {dt}:{di} successfully!') + log.info(f"Open device {dt}:{di} successfully!") return ret != 0 def vci_device_close(dt, di) -> bool: ret = lib.VCI_CloseDevice(c_uint32(dt), c_uint32(di)) if ret == 0: - log.error(f'Failed to open device {dt}:{di}') + log.error(f"Failed to open device {dt}:{di}") else: - log.info(f'Open device {dt}:{di} successfully') + log.info(f"Open device {dt}:{di} successfully") return ret != 0 -def vci_channel_open(dt, di, ch, - clock, - atiming, - dtiming=None, - mode=ZCAN_MODE.NORMAL - ) -> bool: +def vci_channel_open( + dt, di, ch, clock, atiming, dtiming=None, mode=ZCAN_MODE.NORMAL +) -> bool: init = ZCANFD_INIT() - init.mode = mode.value - init.clk = clock - init.atim = atiming - init.dtim = dtiming or atiming + init.mode = mode.value + init.clk = clock + init.atim = atiming + init.dtim = dtiming or atiming ret = lib.VCI_InitCAN(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(init)) if ret == 0: - log.error(f'CH{ch}: Initialize failed') + log.error(f"CH{ch}: Initialize failed") else: - log.info(f'CH{ch}: Initialized') + log.info(f"CH{ch}: Initialized") ret = lib.VCI_StartCAN(c_uint32(dt), c_uint32(di), c_uint32(ch)) if ret == 0: - log.error(f'CH{ch}: Start failed') + log.error(f"CH{ch}: Start failed") else: - log.info(f'CH{ch}: Started') + log.info(f"CH{ch}: Started") return ret != 0 + def vci_channel_close(dt, di, ch) -> bool: ret = lib.VCI_ResetCAN(c_uint32(dt), c_uint32(di), c_uint32(ch)) if ret == 0: - log.error(f'CH{ch}: Close failed') + log.error(f"CH{ch}: Close failed") else: - log.info(f'CH{ch}: Closed') + log.info(f"CH{ch}: Closed") return ret != 0 def vci_channel_read_info(dt, di, ch, info) -> bool: ret = lib.VCI_ReadErrInfo(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(info)) if ret == 0: - log.error(f'CH{ch}: Failed to read error infomation') + log.error(f"CH{ch}: Failed to read error infomation") else: - log.info(f'CH{ch}: Read error infomation successfully') + log.info(f"CH{ch}: Read error infomation successfully") return ret != 0 def vci_channel_enable_tres(dt, di, ch, value) -> bool: ref = ZCAN_REF.TRES.value tres = ZCAN_TRES(enable=ZCAN_TRES.ON if value else ZCAN_TRES.OFF) - ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(tres)) + ret = lib.VCI_SetReference( + c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(tres) + ) if ret == 0: - log.error(f'CH{ch}: Failed to {"enable" if value else "disable"} ' - f'termination resistor') + log.error( + f'CH{ch}: Failed to {"enable" if value else "disable"} ' + f"termination resistor" + ) else: - log.info(f'CH{ch}: {"Enable" if value else "Disable"} ' - f'termination resistor successfully') + log.info( + f'CH{ch}: {"Enable" if value else "Disable"} ' + f"termination resistor successfully" + ) return ret != 0 def vci_channel_set_tx_timeout(dt, di, ch, value) -> bool: ref = ZCAN_REF.TX_TIMEOUT.value timeout = ZCAN_TX_TIMEOUT(value=value) - ret = lib.VCI_SetReference(c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(timeout)) + ret = lib.VCI_SetReference( + c_uint32(dt), c_uint32(di), c_uint32(ch), c_uint32(ref), byref(timeout) + ) if ret == 0: - log.error(f'CH{ch}: Failed to set tx timout {value}ms!') + log.error(f"CH{ch}: Failed to set tx timout {value}ms!") else: - log.debug(f'CH{ch}: Set tx timout to {value}ms') + log.debug(f"CH{ch}: Set tx timout to {value}ms") return ret != 0 def vci_can_send(dt, di, ch, msgs, count) -> int: - ret = lib.VCI_Transmit(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) + ret = lib.VCI_Transmit( + c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count) + ) if ret == 0: - log.error(f'CH{ch}: Failed to send CAN message(s)') + log.error(f"CH{ch}: Failed to send CAN message(s)") else: - log.debug(f'CH{ch}: Sent {ret} CAN message(s)') + log.debug(f"CH{ch}: Sent {ret} CAN message(s)") return ret def vci_canfd_send(dt, di, ch, msgs, count) -> int: - ret = lib.VCI_TransmitFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count)) + ret = lib.VCI_TransmitFD( + c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count) + ) if ret == 0: - log.error(f'CH{ch}: Failed to send CAN-FD message(s)') + log.error(f"CH{ch}: Failed to send CAN-FD message(s)") else: - log.debug(f'CH{ch}: Sent {ret} CAN-FD message(s)') + log.debug(f"CH{ch}: Sent {ret} CAN-FD message(s)") return ret def vci_can_recv(dt, di, ch, msgs, count, delay) -> int: - ret = lib.VCI_Receive(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(delay)) - log.debug(f'CH{ch}: Received {ret} CAN message(s)') + ret = lib.VCI_Receive( + c_uint32(dt), + c_uint32(di), + c_uint32(ch), + byref(msgs), + c_uint32(count), + c_uint32(delay), + ) + log.debug(f"CH{ch}: Received {ret} CAN message(s)") return ret def vci_canfd_recv(dt, di, ch, msgs, count, delay) -> int: - ret = lib.VCI_ReceiveFD(c_uint32(dt), c_uint32(di), c_uint32(ch), byref(msgs), c_uint32(count), c_uint32(delay)) - log.debug(f'CH{ch}: Received {ret} CAN-FD message(s)') + ret = lib.VCI_ReceiveFD( + c_uint32(dt), + c_uint32(di), + c_uint32(ch), + byref(msgs), + c_uint32(count), + c_uint32(delay), + ) + log.debug(f"CH{ch}: Received {ret} CAN-FD message(s)") return ret def vci_can_get_recv_num(dt, di, ch) -> int: ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) - log.debug(f'CH{ch}: {ret} message(s) in CAN FIFO') + log.debug(f"CH{ch}: {ret} message(s) in CAN FIFO") return ret def vci_canfd_get_recv_num(dt, di, ch) -> int: ch = ch | 0x8000_0000 ret = lib.VCI_GetReceiveNum(c_uint32(dt), c_uint32(di), c_uint32(ch)) - log.debug(f'CH{ch&0x7FFF_FFFF}: {ret} message(s) in CAN-FD FIFO') + log.debug(f"CH{ch&0x7FFF_FFFF}: {ret} message(s) in CAN-FD FIFO") return ret diff --git a/test/test_interface_zlg.py b/test/test_interface_zlg.py index f0b99b1ab..89a9823ff 100644 --- a/test/test_interface_zlg.py +++ b/test/test_interface_zlg.py @@ -17,35 +17,35 @@ def setUp(self): self.bitrate = 500_000 self.data_bitrate = 2_000_000 self.bus = can.Bus( - bustype = 'zlg', - channel = self.channel, - device = self.device, - bitrate = self.bitrate, - data_bitrate = self.data_bitrate + bustype="zlg", + channel=self.channel, + device=self.device, + bitrate=self.bitrate, + data_bitrate=self.data_bitrate, ) self.can_std_msg = can.Message( - arbitration_id = self.std_id, - is_extended_id = False, - is_fd = False, - data = list(range(8)) + arbitration_id=self.std_id, + is_extended_id=False, + is_fd=False, + data=list(range(8)), ) self.can_ext_msg = can.Message( - arbitration_id = self.ext_id, - is_extended_id = True, - is_fd = False, - data = list(range(8)) + arbitration_id=self.ext_id, + is_extended_id=True, + is_fd=False, + data=list(range(8)), ) self.canfd_std_msg = can.Message( - arbitration_id = self.std_id, - is_extended_id = False, - is_fd = True, - data = list(range(64)) + arbitration_id=self.std_id, + is_extended_id=False, + is_fd=True, + data=list(range(64)), ) self.canfd_ext_msg = can.Message( - arbitration_id = self.ext_id, - is_extended_id = True, - is_fd = True, - data = list(range(64)) + arbitration_id=self.ext_id, + is_extended_id=True, + is_fd=True, + data=list(range(64)), ) def tearDown(self): @@ -55,30 +55,30 @@ def testSendStdMsg(self): try: self.bus.send(self.can_std_msg) except CanOperationError as ex: - self.fail('Failed to send CAN std frame') + self.fail("Failed to send CAN std frame") def testSendExtMsg(self): try: self.bus.send(self.can_ext_msg) except CanOperationError as ex: - self.fail('Failed to send CAN ext frame') + self.fail("Failed to send CAN ext frame") def testSendStdMsgFD(self): try: self.bus.send(self.canfd_std_msg) except CanOperationError as ex: - self.fail('Failed to send CANFD std frame') + self.fail("Failed to send CANFD std frame") def testSendExtMsgFD(self): try: self.bus.send(self.canfd_ext_msg) except CanOperationError as ex: - self.fail('Failed to send CANFD ext frame') + self.fail("Failed to send CANFD ext frame") def testSendTimeout0S(self): try: timeout = 0 - print(f'Set send {timeout=}') + print(f"Set send {timeout=}") t1 = time.time() self.bus.send(self.can_std_msg, timeout) t2 = time.time() @@ -89,29 +89,29 @@ def testSendTimeout0S(self): def testSendTimeout1S(self): try: timeout = 1 - print(f'Set send {timeout=}') + print(f"Set send {timeout=}") t1 = time.time() self.bus.send(self.can_std_msg, timeout) t2 = time.time() - self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + self.assertAlmostEqual(t1, t2, delta=timeout * 1.1) except Exception as ex: self.fail(str(ex)) def testSendTimeout5S(self): try: timeout = 5 - print(f'Set send {timeout=}') + print(f"Set send {timeout=}") t1 = time.time() self.bus.send(self.can_std_msg, timeout) t2 = time.time() - self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + self.assertAlmostEqual(t1, t2, delta=timeout * 1.1) except Exception as ex: self.fail(str(ex)) def testSendTimeoutForever(self): try: timeout = None - print(f'Set send {timeout=}') + print(f"Set send {timeout=}") self.bus.send(self.can_std_msg, timeout) except Exception as ex: self.fail(str(ex)) @@ -123,7 +123,7 @@ def testRecv(self): def testRecvTimeout0S(self): try: timeout = 0 - print(f'Set receive {timeout=}') + print(f"Set receive {timeout=}") t1 = time.time() msg = self.bus.recv(timeout) t2 = time.time() @@ -134,29 +134,29 @@ def testRecvTimeout0S(self): def testRecvTimeout1S(self): try: timeout = 1 - print(f'Set receive {timeout=}') + print(f"Set receive {timeout=}") t1 = time.time() msg = self.bus.recv(timeout) t2 = time.time() - self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + self.assertAlmostEqual(t1, t2, delta=timeout * 1.1) except Exception as ex: self.fail(str(ex)) def testRecvTimeout5S(self): try: timeout = 5 - print(f'Set receive {timeout=}') + print(f"Set receive {timeout=}") t1 = time.time() msg = self.bus.recv(timeout) t2 = time.time() - self.assertAlmostEqual(t1, t2, delta=timeout*1.1) + self.assertAlmostEqual(t1, t2, delta=timeout * 1.1) except Exception as ex: self.fail(str(ex)) def testRecvTimeoutForever(self): try: timeout = None - print(f'Set receive {timeout=}') + print(f"Set receive {timeout=}") msg = self.bus.recv(timeout) except Exception as ex: self.fail(str(ex)) From fad194a0e8334a8a9250e6a7b33cbe5d67e353e3 Mon Sep 17 00:00:00 2001 From: Keelung Yang Date: Mon, 24 Apr 2023 13:22:24 +0800 Subject: [PATCH 17/17] Restore changes to avoid conflicts --- can/exceptions.py | 10 ++++++++++ doc/configuration.rst | 2 ++ doc/interfaces.rst | 1 + 3 files changed, 13 insertions(+) diff --git a/can/exceptions.py b/can/exceptions.py index e1c970e27..9a7a6201c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -9,6 +9,7 @@ +-- CanInitializationError +-- CanOperationError +-- CanTimeoutError + +-- CanBitRateError Keep in mind that some functions and methods may raise different exceptions. For example, validating typical arguments and parameters might result in a @@ -111,6 +112,15 @@ class CanTimeoutError(CanError, TimeoutError): """ +class CanBitRateError(CanError): + """Indicates the invalid / unsupported bitrate. + + Example scenarios: + - Invalid / unsupported bitrate on bus + - Can't convert between bit timing and bitrate + """ + + @contextmanager def error_check( error_message: Optional[str] = None, diff --git a/doc/configuration.rst b/doc/configuration.rst index 494351350..be81a1131 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -156,6 +156,8 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"vector"`` | :doc:`interfaces/vector` | +---------------------+-------------------------------------+ +| ``"zlg"`` | :doc:`interfaces/zlg` | ++---------------------+-------------------------------------+ | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 6645ec338..6bfaa4b98 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -38,6 +38,7 @@ The following hardware interfaces are included in python-can: interfaces/systec interfaces/usb2can interfaces/vector + interfaces/zlg Additional interface types can be added via the :ref:`plugin interface`, or by installing a third party package that utilises the :ref:`plugin interface`.