diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index 347caed50..1419d97a1 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,7 +1,7 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems -Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2016-2021 Giuseppe Corbelli """ from can.interfaces.ixxat.canlib import IXXATBus diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 4dc0d3e6e..1e4055f89 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -2,6 +2,8 @@ import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 from can import BusABC, Message +from can.bus import BusState + from typing import Optional @@ -11,7 +13,8 @@ class IXXATBus(BusABC): Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN, the other with CAN-FD. - This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) class depending on fd user option. + This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) + class depending on fd user option. """ def __init__( @@ -140,8 +143,18 @@ def _recv_internal(self, timeout): def send(self, msg: Message, timeout: Optional[float] = None) -> None: return self.bus.send(msg, timeout) - def _send_periodic_internal(self, msg, period, duration=None): - return self.bus._send_periodic_internal(msg, period, duration) + def _send_periodic_internal(self, msgs, period, duration=None): + return self.bus._send_periodic_internal(msgs, period, duration) def shutdown(self): return self.bus.shutdown() + + @property + def state(self) -> BusState: + """ + Return the current state of the hardware + """ + return self.bus.state + + +# ~class IXXATBus(BusABC): --------------------------------------------------- diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index bdb05cda5..fa88e5f90 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems TODO: We could implement this interface such that setting other filters could work when the initial filters were set to zero using the @@ -16,6 +16,7 @@ from typing import Optional, Callable, Tuple from can import BusABC, Message +from can.bus import BusState from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, @@ -38,7 +39,6 @@ log = logging.getLogger("can.ixxat") -from time import perf_counter # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -225,6 +225,13 @@ def __check_status(result, function, args): (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status, ) + # HRESULT canChannelGetStatus (HANDLE hCanChn, PCANCHANSTATUS pStatus ); + _canlib.map_symbol( + "canChannelGetStatus", + ctypes.c_long, + (HANDLE, structures.PCANCHANSTATUS), + __check_status, + ) # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); _canlib.map_symbol( @@ -509,11 +516,11 @@ def __init__( == bytes(unique_hardware_id, "ascii") ): break - else: - log.debug( - "Ignoring IXXAT with hardware id '%s'.", - self._device_info.UniqueHardwareId.AsChar.decode("ascii"), - ) + + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) _canlib.vciEnumDeviceClose(self._device_handle) try: @@ -522,7 +529,9 @@ def __init__( ctypes.byref(self._device_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open device: {exception}") + raise CanInitializationError( + f"Could not open device: {exception}" + ) from exception log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -543,7 +552,7 @@ def __init__( except Exception as exception: raise CanInitializationError( f"Could not open and initialize channel: {exception}" - ) + ) from exception # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize( @@ -637,85 +646,88 @@ def flush_tx_buffer(self): def _recv_internal(self, timeout): """Read a message from IXXAT device.""" - - # TODO: handling CAN error messages? data_received = False - if timeout == 0: + if self._inWaiting() or timeout == 0: # Peek without waiting - try: - _canlib.canChannelPeekMessage( - self._channel_handle, ctypes.byref(self._message) - ) - except (VCITimeout, VCIRxQueueEmptyError): - return None, True - else: - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True + recv_function = functools.partial( + _canlib.canChannelPeekMessage, + self._channel_handle, + ctypes.byref(self._message), + ) else: # Wait if no message available - if timeout is None or timeout < 0: - remaining_ms = constants.INFINITE - t0 = None - else: - timeout_ms = int(timeout * 1000) - remaining_ms = timeout_ms - t0 = perf_counter() - - while True: - try: - _canlib.canChannelReadMessage( - self._channel_handle, remaining_ms, ctypes.byref(self._message) + timeout = ( + constants.INFINITE + if (timeout is None or timeout < 0) + else int(timeout * 1000) + ) + recv_function = functools.partial( + _canlib.canChannelReadMessage, + self._channel_handle, + timeout, + ctypes.byref(self._message), + ) + + try: + recv_function() + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, overall timeout is handled by BusABC.recv + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + f"Unknown CAN info message code {self._message.abData[0]}", ) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the perf_counter() - pass + ) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: + if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR: + log.warning("CAN error: data overrun") else: - # See if we got a data or info/error messages - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - break - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info( - CAN_INFO_MESSAGES.get( - self._message.abData[0], - "Unknown CAN info message code {}".format( - self._message.abData[0] - ), - ) - ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR - ): - log.warning( - CAN_ERROR_MESSAGES.get( - self._message.abData[0], - "Unknown CAN error message code {}".format( - self._message.abData[0] - ), - ) + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + f"Unknown CAN error message code {self._message.abData[0]}", ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS - ): - log.info(_format_can_status(self._message.abData[0])) - if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: - raise VCIBusOffError() - - elif ( - self._message.uMsgInfo.Bits.type - == constants.CAN_MSGTYPE_TIMEOVR - ): - pass - else: - log.warning("Unexpected message info type") - - if t0 is not None: - remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000) - if remaining_ms < 0: - break + ) + log.warning( + "CAN message flags bAddFlags/bFlags2 0x%02X bflags 0x%02X", + self._message.uMsgInfo.Bytes.bAddFlags, + self._message.uMsgInfo.Bytes.bFlags, + ) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warning( + "Unexpected message info type 0x%X", + self._message.uMsgInfo.Bits.type, + ) + finally: + if not data_received: + # Check hard errors + status = structures.CANLINESTATUS() + _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status)) + error_byte_1 = status.dwStatus & 0x0F + error_byte_2 = status.dwStatus & 0xF0 + if error_byte_1 > constants.CAN_STATUS_TXPEND: + # CAN_STATUS_OVRRUN = 0x02 # data overrun occurred + # CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded + # CAN_STATUS_BUSOFF = 0x08 # bus off status + if error_byte_1 & constants.CAN_STATUS_OVRRUN: + raise VCIError("Data overrun occurred") + elif error_byte_1 & constants.CAN_STATUS_ERRLIM: + raise VCIError("Error warning limit exceeded") + elif error_byte_1 & constants.CAN_STATUS_BUSOFF: + raise VCIError("Bus off status") + elif error_byte_2 > constants.CAN_STATUS_ININIT: + # CAN_STATUS_BUSCERR = 0x20 # bus coupling error + if error_byte_2 & constants.CAN_STATUS_BUSCERR: + raise VCIError("Bus coupling error") if not data_received: # Timed out / can message type is not DATA @@ -764,11 +776,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: _canlib.canChannelSendMessage( self._channel_handle, int(timeout * 1000), message ) - else: _canlib.canChannelPostMessage(self._channel_handle, message) + # Want to log outgoing messages? + # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message) - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() @@ -778,7 +791,7 @@ def _send_periodic_internal(self, msg, period, duration=None): self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msg, period, duration, self._scheduler_resolution + self._scheduler, msgs, period, duration, self._scheduler_resolution ) def shutdown(self): @@ -786,9 +799,35 @@ def shutdown(self): _canlib.canSchedulerClose(self._scheduler) _canlib.canChannelClose(self._channel_handle) _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlReset(self._control_handle) _canlib.canControlClose(self._control_handle) _canlib.vciDeviceClose(self._device_handle) + @property + def state(self) -> BusState: + """ + Return the current state of the hardware + """ + status = structures.CANLINESTATUS() + _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status)) + if status.bOpMode == constants.CAN_OPMODE_LISTONLY: + return BusState.PASSIVE + + error_byte_1 = status.dwStatus & 0x0F + # CAN_STATUS_BUSOFF = 0x08 # bus off status + if error_byte_1 & constants.CAN_STATUS_BUSOFF: + return BusState.ERROR + + error_byte_2 = status.dwStatus & 0xF0 + # CAN_STATUS_BUSCERR = 0x20 # bus coupling error + if error_byte_2 & constants.CAN_STATUS_BUSCERR: + raise BusState.ERROR + + return BusState.ACTIVE + + +# ~class IXXATBus(BusABC): --------------------------------------------------- + class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 802168630..108ad2c02 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -825,9 +825,7 @@ def _recv_internal(self, timeout): log.info( CAN_INFO_MESSAGES.get( self._message.abData[0], - "Unknown CAN info message code {}".format( - self._message.abData[0] - ), + f"Unknown CAN info message code {self._message.abData[0]}", ) ) @@ -837,9 +835,7 @@ def _recv_internal(self, timeout): log.warning( CAN_ERROR_MESSAGES.get( self._message.abData[0], - "Unknown CAN error message code {}".format( - self._message.abData[0] - ), + f"Unknown CAN error message code {self._message.abData[0]}", ) ) @@ -933,7 +929,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: else: _canlib.canChannelPostMessage(self._channel_handle, message) - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() @@ -945,7 +941,7 @@ def _send_periodic_internal(self, msg, period, duration=None): ) # TODO: confirm _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msg, period, duration, self._scheduler_resolution + self._scheduler, msgs, period, duration, self._scheduler_resolution ) def shutdown(self): diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index 1dbc22a44..3bc1aa42e 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli """ @@ -106,20 +106,21 @@ VCI_E_WRONG_FLASHFWVERSION = SEV_VCI_ERROR | 0x001A # Controller status -CAN_STATUS_TXPEND = 0x01 -CAN_STATUS_OVRRUN = 0x02 -CAN_STATUS_ERRLIM = 0x04 -CAN_STATUS_BUSOFF = 0x08 -CAN_STATUS_ININIT = 0x10 -CAN_STATUS_BUSCERR = 0x20 +CAN_STATUS_TXPEND = 0x01 # transmission pending +CAN_STATUS_OVRRUN = 0x02 # data overrun occurred +CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded +CAN_STATUS_BUSOFF = 0x08 # bus off status +CAN_STATUS_ININIT = 0x10 # init mode active +CAN_STATUS_BUSCERR = 0x20 # bus coupling error # Controller operating modes -CAN_OPMODE_UNDEFINED = 0x00 -CAN_OPMODE_STANDARD = 0x01 -CAN_OPMODE_EXTENDED = 0x02 -CAN_OPMODE_ERRFRAME = 0x04 -CAN_OPMODE_LISTONLY = 0x08 -CAN_OPMODE_LOWSPEED = 0x10 +CAN_OPMODE_UNDEFINED = 0x00 # undefined +CAN_OPMODE_STANDARD = 0x01 # reception of 11-bit id messages +CAN_OPMODE_EXTENDED = 0x02 # reception of 29-bit id messages +CAN_OPMODE_ERRFRAME = 0x04 # reception of error frames +CAN_OPMODE_LISTONLY = 0x08 # listen only mode (TX passive) +CAN_OPMODE_LOWSPEED = 0x10 # use low speed bus interface +CAN_OPMODE_AUTOBAUD = 0x20 # automatic bit rate detection # Extended operating modes CAN_EXMODE_DISABLED = 0x00 @@ -167,13 +168,14 @@ CAN_FILTER_EXCL = 0x04 # exclusive filtering (inhibit registered IDs) +# message information flags (used by ) CAN_MSGFLAGS_DLC = 0x0F # [bit 0] data length code CAN_MSGFLAGS_OVR = 0x10 # [bit 4] data overrun flag CAN_MSGFLAGS_SRR = 0x20 # [bit 5] self reception request CAN_MSGFLAGS_RTR = 0x40 # [bit 6] remote transmission request CAN_MSGFLAGS_EXT = 0x80 # [bit 7] frame format (0=11-bit, 1=29-bit) - +# extended message information flags (used by ) CAN_MSGFLAGS2_SSM = 0x01 # [bit 0] single shot mode CAN_MSGFLAGS2_HPM = 0x02 # [bit 1] high priority message CAN_MSGFLAGS2_EDL = 0x04 # [bit 2] extended data length diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index babe08e3b..50b84dfa4 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli Copyright (C) 2019 Marcel Kanter diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index f76a39a38..b784437e0 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli """ @@ -70,11 +70,13 @@ def __str__(self): class CANLINESTATUS(ctypes.Structure): _fields_ = [ + # current CAN operating mode. Value is a logical combination of + # one or more CAN_OPMODE_xxx constants ("bOpMode", ctypes.c_uint8), - ("bBtReg0", ctypes.c_uint8), - ("bBtReg1", ctypes.c_uint8), - ("bBusLoad", ctypes.c_uint8), - ("dwStatus", ctypes.c_uint32), + ("bBtReg0", ctypes.c_uint8), # current bus timing register 0 value + ("bBtReg1", ctypes.c_uint8), # current bus timing register 1 value + ("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100) + ("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_) ] @@ -83,11 +85,11 @@ class CANLINESTATUS(ctypes.Structure): class CANCHANSTATUS(ctypes.Structure): _fields_ = [ - ("sLineStatus", CANLINESTATUS), - ("fActivated", ctypes.c_uint32), - ("fRxOverrun", ctypes.c_uint32), - ("bRxFifoLoad", ctypes.c_uint8), - ("bTxFifoLoad", ctypes.c_uint8), + ("sLineStatus", CANLINESTATUS), # current CAN line status + ("fActivated", ctypes.c_uint32), # TRUE if the channel is activated + ("fRxOverrun", ctypes.c_uint32), # TRUE if receive FIFO overrun occurred + ("bRxFifoLoad", ctypes.c_uint8), # receive FIFO load in percent (0..100) + ("bTxFifoLoad", ctypes.c_uint8), # transmit FIFO load in percent (0..100) ] @@ -118,7 +120,7 @@ class Bytes(ctypes.Structure): ( "bAddFlags", ctypes.c_uint8, - ), # extended flags (see CAN_MSGFLAGS2_ constants) + ), # extended flags (see CAN_MSGFLAGS2_ constants). AKA bFlags2 in VCI v4 ("bFlags", ctypes.c_uint8), # flags (see CAN_MSGFLAGS_ constants) ("bAccept", ctypes.c_uint8), # accept code (see CAN_ACCEPT_ constants) ] @@ -153,11 +155,20 @@ class Bits(ctypes.Structure): class CANMSG(ctypes.Structure): _fields_ = [ ("dwTime", ctypes.c_uint32), + # CAN ID of the message in Intel format (aligned right) without RTR bit. ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), ("abData", ctypes.c_uint8 * 8), ] + def __str__(self) -> str: + return """ID: 0x{0:04x}{1} DLC: {2:02d} DATA: {3}""".format( + self.dwMsgId, + "[RTR]" if self.uMsgInfo.Bits.rtr else "", + self.uMsgInfo.Bits.dlc, + memoryview(self.abData)[: self.uMsgInfo.Bits.dlc].hex(sep=" "), + ) + PCANMSG = ctypes.POINTER(CANMSG)