From 01b9188b8c76217a19323ca85352d7555139e88b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Mon, 29 Apr 2019 21:01:09 +0200 Subject: [PATCH 01/28] Refactoring: Move exceptions to separate file. --- can/__init__.py | 10 ++-------- can/exceptions.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 can/exceptions.py diff --git a/can/__init__.py b/can/__init__.py index cb4b6d280..c93a1b208 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,19 +8,13 @@ import logging -__version__ = "3.2.0-a0" +__version__ = "3.2.0-exception-handling" log = logging.getLogger('can') rc = dict() - -class CanError(IOError): - """Indicates an error with the CAN network. - - """ - pass - +from .exceptions import CanError, CanInitializationError, CanOperationError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/exceptions.py b/can/exceptions.py new file mode 100644 index 000000000..131bd342c --- /dev/null +++ b/can/exceptions.py @@ -0,0 +1,25 @@ +""" +Exception classes. + +Copyright (c) Marcel Kanter +""" + +class CanError(Exception): + """ Base class for all can related exceptions. + """ + pass + + +class CanInitializationError(CanError): + """ Indicates an error related to the initialization. + """ + pass + + +class CanOperationError(CanError): + """ Indicates an error while operation. + For example: + ACK error (e.g. only one bus member) + Stuff, CRC error (e.g. malformed message) + """ + pass \ No newline at end of file From 8697f14b758450ec5b35ef57f300c7abd1ffd714 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Mon, 29 Apr 2019 21:57:51 +0200 Subject: [PATCH 02/28] Added tests for ixxat interface. Started to use the new exception types. --- can/interfaces/ixxat/canlib.py | 1021 ++++++++++++++-------------- can/interfaces/ixxat/exceptions.py | 21 +- test/test_interface_ixxat.py | 39 ++ 3 files changed, 560 insertions(+), 521 deletions(-) create mode 100644 test/test_interface_ixxat.py diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..04846562c 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -4,12 +4,13 @@ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2019 Marcel Kanter TODO: We could implement this interface such that setting other filters - could work when the initial filters were set to zero using the - software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! - See also the NICAN interface. + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. """ @@ -20,9 +21,9 @@ import logging import sys -from can import CanError, BusABC, Message -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC) +from can import BusABC, Message +from can import CanError, CanInitializationError +from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures @@ -33,10 +34,10 @@ log = logging.getLogger('can.ixxat') try: - # since Python 3.3 - from time import perf_counter as _timer_function + # since Python 3.3 + from time import perf_counter as _timer_function except ImportError: - from time import clock as _timer_function + from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -44,534 +45,532 @@ # main ctypes instance _canlib = None if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) elif sys.platform == "cygwin": - try: - _canlib = CLibrary("vcinpl.dll") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) else: - # Will not work on other systems, but have it importable anyway for - # tests/sphinx - log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """ Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string - """ - #TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT), - arguments - ) + """ Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :param arguments: + Arbitrary arguments tuple + :return: + Formatted string + """ + #TODO: make sure we don't generate another exception + return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) def __vciFormatError(library_instance, function, HRESULT): - """ Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string - """ - buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + """ Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) def __check_status(result, function, arguments): - """ - Check the result of a vcinpl function call and raise appropriate exception - in case of an error. Used as errcheck function when mapping C functions - with ctypes. - :param result: - Function call numeric result - :param callable function: - Called function - :param arguments: - Arbitrary arguments tuple - :raise: - :class:VCITimeout - :class:VCIRxQueueEmptyError - :class:StopIteration - :class:VCIError - """ - if isinstance(result, int): - # Real return value is an unsigned long - result = ctypes.c_ulong(result).value - - if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) - elif result == constants.VCI_E_RXQUEUE_EMPTY: - raise VCIRxQueueEmptyError() - elif result == constants.VCI_E_NO_MORE_ITEMS: - raise StopIteration() - elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus - elif result != constants.VCI_OK: - raise VCIError(vciFormatError(function, result)) - - return result + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param arguments: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if isinstance(result, int): + # Real return value is an unsigned long + result = ctypes.c_ulong(result).value + + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result try: - # Map all required symbols and initialize library --------------------------- - #HRESULT VCIAPI vciInitialize ( void ); - _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - - #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) - # Hack to have vciFormatError as a free function - vciFormatError = functools.partial(__vciFormatError, _canlib) - - # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) - - # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) - # HRESULT vciDeviceClose( HANDLE hDevice ) - _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - - # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - - #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) - #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) - #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) - #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) - #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - _canlib.vciInitialize() + # Map all required symbols and initialize library + #HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) + + #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); + _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + + #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) + #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) + #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) + #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) + #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + _canlib.vciInitialize() except AttributeError: - # In case _canlib == None meaning we're not on win32/no lib found - pass + # In case _canlib == None meaning we're not on win32/no lib found + pass except Exception as e: - log.warning("Could not initialize IXXAT VCI library: %s", e) + log.warning("Could not initialize IXXAT VCI library: %s", e) # --------------------------------------------------------------------------- CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } #---------------------------------------------------------------------------- class IXXATBus(BusABC): - """The CAN Bus implemented for the IXXAT interface. - - .. warning:: - - This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. - - """ - - CHANNEL_BITRATES = { - 0: { - 10000: constants.CAN_BT0_10KB, - 20000: constants.CAN_BT0_20KB, - 50000: constants.CAN_BT0_50KB, - 100000: constants.CAN_BT0_100KB, - 125000: constants.CAN_BT0_125KB, - 250000: constants.CAN_BT0_250KB, - 500000: constants.CAN_BT0_500KB, - 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB - }, - 1: { - 10000: constants.CAN_BT1_10KB, - 20000: constants.CAN_BT1_20KB, - 50000: constants.CAN_BT1_50KB, - 100000: constants.CAN_BT1_100KB, - 125000: constants.CAN_BT1_125KB, - 250000: constants.CAN_BT1_250KB, - 500000: constants.CAN_BT1_500KB, - 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } - } - - def __init__(self, channel, can_filters=None, **kwargs): - """ - :param int channel: - The Channel id to create this bus with. - - :param list can_filters: - See :meth:`can.BusABC.set_filters`. - - :param bool receive_own_messages: - Enable self-reception of sent messages. - - :param int UniqueHardwareId: - UniqueHardwareId to connect (optional, will use the first found if not supplied) - - :param int bitrate: - Channel bitrate in bit/s - """ - if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") - log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", kwargs) - # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) - # Usually comes as a string from the config file - channel = int(channel) - - if (bitrate not in self.CHANNEL_BITRATES[0]): - raise ValueError("Invalid bitrate {}".format(bitrate)) - - self._device_handle = HANDLE() - self._device_info = structures.VCIDEVICEINFO() - self._control_handle = HANDLE() - self._channel_handle = HANDLE() - self._channel_capabilities = structures.CANCAPABILITIES() - self._message = structures.CANMSG() - self._payload = (ctypes.c_byte * 8)() - - # Search for supplied device - if UniqueHardwareId is None: - log.info("Searching for first available device") - else: - log.info("Searching for unique HW ID %s", UniqueHardwareId) - _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) - except StopIteration: - if (UniqueHardwareId is None): - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") - else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) - else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): - break - else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) - _canlib.vciEnumDeviceClose(self._device_handle) - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) - log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) - - log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) - # Signal TX/RX events when at least one frame has been handled - _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) - _canlib.canChannelActivate(self._channel_handle, constants.TRUE) - - log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) - _canlib.canControlInitialize( - self._control_handle, - constants.CAN_OPMODE_STANDARD|constants.CAN_OPMODE_EXTENDED|constants.CAN_OPMODE_ERRFRAME, - self.CHANNEL_BITRATES[0][bitrate], - self.CHANNEL_BITRATES[1][bitrate] - ) - _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) - - # With receive messages, this field contains the relative reception time of - # the message in ticks. The resolution of a tick can be calculated from the fields - # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: - # frequency [1/s] = dwClockFreq / dwTscDivisor - # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) - - # Setup filters before starting the channel - if can_filters: - log.info("The IXXAT VCI backend is filtering messages") - # Disable every message coming in - for extended in (0, 1): - _canlib.canControlSetAccFilter(self._control_handle, - extended, - constants.CAN_ACC_CODE_NONE, - constants.CAN_ACC_MASK_NONE) - for can_filter in can_filters: - # Whitelist - code = int(can_filter['can_id']) - mask = int(can_filter['can_mask']) - extended = can_filter.get('extended', False) - _canlib.canControlAddFilterIds(self._control_handle, - 1 if extended else 0, - code << 1, - mask << 1) - log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) - - # Start the CAN controller. Messages will be forwarded to the channel - _canlib.canControlStart(self._control_handle, constants.TRUE) - - # For cyclic transmit list. Set when .send_periodic() is first called - self._scheduler = None - self._scheduler_resolution = None - self.channel = channel - - # Usually you get back 3 messages like "CAN initialized" ecc... - # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): - try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - break - - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) - - def _inWaiting(self): - try: - _canlib.canChannelWaitRxEvent(self._channel_handle, 0) - except VCITimeout: - return 0 - else: - return 1 - - def flush_tx_buffer(self): - """ Flushes the transmit buffer on the IXXAT """ - # TODO #64: no timeout? - _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) - - def _recv_internal(self, timeout): - """ Read a message from IXXAT device. """ - - # TODO: handling CAN error messages? - data_received = False - - if 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 - 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 = _timer_function() - - while True: - try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the _timer_function() - 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 - 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]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: - pass - else: - log.warn("Unexpected message info type") - - if t0 is not None: - remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) - if remaining_ms < 0: - break - - if not data_received: - # Timed out / can message type is not DATA - return None, True - - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 - rx_msg = Message( - timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s - is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, - arbitration_id=self._message.dwMsgId, - dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], - channel=self.channel - ) - - return rx_msg, True - - def send(self, msg, timeout=None): - - # This system is not designed to be very efficient - message = structures.CANMSG() - message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 - message.dwMsgId = msg.arbitration_id - if msg.dlc: - message.uMsgInfo.Bits.dlc = msg.dlc - adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) - ctypes.memmove(message.abData, adapter, len(msg.data)) - - if timeout: - _canlib.canChannelSendMessage( - self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - - def _send_periodic_internal(self, msg, period, duration=None): - """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, - self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, - self._scheduler_resolution) - - def shutdown(self): - if self._scheduler is not None: - _canlib.canSchedulerClose(self._scheduler) - _canlib.canChannelClose(self._channel_handle) - _canlib.canControlStart(self._control_handle, constants.FALSE) - _canlib.canControlClose(self._control_handle) - _canlib.vciDeviceClose(self._device_handle) - - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + CHANNEL_BITRATES = { + 0: { + 10000: constants.CAN_BT0_10KB, + 20000: constants.CAN_BT0_20KB, + 50000: constants.CAN_BT0_50KB, + 100000: constants.CAN_BT0_100KB, + 125000: constants.CAN_BT0_125KB, + 250000: constants.CAN_BT0_250KB, + 500000: constants.CAN_BT0_500KB, + 800000: constants.CAN_BT0_800KB, + 1000000: constants.CAN_BT0_1000KB + }, + 1: { + 10000: constants.CAN_BT1_10KB, + 20000: constants.CAN_BT1_20KB, + 50000: constants.CAN_BT1_50KB, + 100000: constants.CAN_BT1_100KB, + 125000: constants.CAN_BT1_125KB, + 250000: constants.CAN_BT1_250KB, + 500000: constants.CAN_BT1_500KB, + 800000: constants.CAN_BT1_800KB, + 1000000: constants.CAN_BT1_1000KB + } + } + + def __init__(self, channel, can_filters=None, **kwargs): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + :param bool receive_own_messages: + Enable self-reception of sent messages. + + :param int UniqueHardwareId: + UniqueHardwareId to connect (optional, will use the first found if not supplied) + + :param int bitrate: + Channel bitrate in bit/s + """ + if _canlib is None: + raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + log.info("CAN Filters: %s", can_filters) + log.info("Got configuration of: %s", kwargs) + # Configuration options + bitrate = kwargs.get('bitrate', 500000) + UniqueHardwareId = kwargs.get('UniqueHardwareId', None) + rxFifoSize = kwargs.get('rxFifoSize', 16) + txFifoSize = kwargs.get('txFifoSize', 16) + self._receive_own_messages = kwargs.get('receive_own_messages', False) + # Usually comes as a string from the config file + channel = int(channel) + + if (bitrate not in self.CHANNEL_BITRATES[0]): + raise ValueError("Invalid bitrate {}".format(bitrate)) + + if rxFifoSize <= 0: + raise ValueError("rxFifoSize must be > 0") + + if txFifoSize <= 0: + raise ValueError("txFifoSize must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES() + self._message = structures.CANMSG() + self._payload = (ctypes.c_byte * 8)() + + # Search for supplied device + if UniqueHardwareId is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", UniqueHardwareId) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + except StopIteration: + if (UniqueHardwareId is None): + raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + else: + raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + else: + if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + break + else: + log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + except: + raise(CanInitializationError("Could not open device.")) + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) + + try: + _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + # Signal TX/RX events when at least one frame has been handled + except: + raise(CanInitializationError("Could not open and initialize channel.")) + + _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + log.info("Initializing control %d bitrate %d", channel, bitrate) + _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) + _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) + _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + # We explicitly cast to float for Python 2.x users + self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) + for can_filter in can_filters: + # Whitelist + code = int(can_filter['can_id']) + mask = int(can_filter['can_mask']) + extended = can_filter.get('extended', False) + _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for i in range(rxFifoSize): + try: + _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """ Flushes the transmit buffer on the IXXAT """ + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """ Read a message from IXXAT device. """ + + # TODO: handling CAN error messages? + data_received = False + + if 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 + 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 = _timer_function() + + while True: + try: + _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the _timer_function() + 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 + 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]))) + + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warn("Unexpected message info type") + + if t0 is not None: + remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s + is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, + is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + arbitration_id=self._message.dwMsgId, + dlc=self._message.uMsgInfo.Bits.dlc, + data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], + channel=self.channel + ) + + return rx_msg, True + + def send(self, msg, timeout=None): + + # This system is not designed to be very efficient + message = structures.CANMSG() + message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: + message.uMsgInfo.Bits.dlc = msg.dlc + adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) + ctypes.memmove(message.abData, adapter, len(msg.data)) + + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. + """ + if self.__set_filters_has_been_called: + log.warn("using filters is not supported like this, see note on IXXATBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): - """A message in the cyclic transmit list.""" - - def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) - self._scheduler = scheduler - self._index = None - self._count = int(duration / period) if duration else 0 - - self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = msg.arbitration_id - self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.data): - self._msg.abData[i] = b - self.start() - - def start(self): - """Start transmitting message (add to list if needed).""" - if self._index is None: - self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, - self._msg, - self._index) - _canlib.canSchedulerStartMessage(self._scheduler, - self._index, - self._count) - - def pause(self): - """Pause transmitting message (keep it in the list).""" - _canlib.canSchedulerStopMessage(self._scheduler, self._index) - - def stop(self): - """Stop transmitting message (remove from list).""" - # Remove it completely instead of just stopping it to avoid filling up - # the list with permanently stopped messages - _canlib.canSchedulerRemMessage(self._scheduler, self._index) - self._index = None + RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msg, period, duration, resolution): + super(CyclicSendTask, self).__init__(msg, period, duration) + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = msg.arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = msg.dlc + for i, b in enumerate(msg.data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index ac1700dca..03c3b7167 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -4,28 +4,29 @@ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2019 Marcel Kanter """ -from can import CanError +from can import CanError, CanInitializationError __all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] class VCITimeout(CanError): - """ Wraps the VCI_E_TIMEOUT error """ - pass + """ Wraps the VCI_E_TIMEOUT error """ + pass class VCIError(CanError): - """ Try to display errors that occur within the wrapped C library nicely. """ - pass + """ Try to display errors that occur within the wrapped C library nicely. """ + pass class VCIRxQueueEmptyError(VCIError): - """ Wraps the VCI_E_RXQUEUE_EMPTY error """ - def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): + super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") -class VCIDeviceNotFoundError(CanError): - pass +class VCIDeviceNotFoundError(CanInitializationError): + pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py new file mode 100644 index 000000000..76c77052d --- /dev/null +++ b/test/test_interface_ixxat.py @@ -0,0 +1,39 @@ +""" +Unittest for ixxat interface. + +Copyright (C) 2019 Marcel Kanter +""" + +import unittest + +import can + +from can import CanInitializationError + + +class InterfaceIxxatTestCase(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = -1) + + # rxFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + + # txFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + + # non-existent channel -> use arbitrary high value + with self.assertRaises(CanInitializationError): + bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From b9bea051efd0e12c1766cbc5a6ec5287f68abf3f Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 09:57:04 +0200 Subject: [PATCH 03/28] Introduced CanBackEndError for exceptions related to the backend. --- can/__init__.py | 2 +- can/exceptions.py | 14 +++++++++++--- can/interfaces/ixxat/canlib.py | 7 +++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index c93a1b208..d377652d0 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -14,7 +14,7 @@ rc = dict() -from .exceptions import CanError, CanInitializationError, CanOperationError +from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/exceptions.py b/can/exceptions.py index 131bd342c..d43a27648 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -10,16 +10,24 @@ class CanError(Exception): pass +class CanBackEndError(CanError): + """ Indicates an error related to the backend (e.g. driver/OS/library) + Examples: + - A call to a library function results in an unexpected return value + """ + pass + + class CanInitializationError(CanError): """ Indicates an error related to the initialization. + Examples for situations when this exception may occur: + - Try to open a non-existent device and/or channel + - Try to use a invalid setting, which is ok by value, but not ok for the interface """ pass class CanOperationError(CanError): """ Indicates an error while operation. - For example: - ACK error (e.g. only one bus member) - Stuff, CRC error (e.g. malformed message) """ pass \ No newline at end of file diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 04846562c..7fae57f97 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -22,7 +22,7 @@ import sys from can import BusABC, Message -from can import CanError, CanInitializationError +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT @@ -452,17 +452,16 @@ def _recv_internal(self, timeout): 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]))) - + # TODO report error with is_error_frame is set to true elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: pass else: log.warn("Unexpected message info type") + raise(CanBackEndError()) if t0 is not None: remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) From 938e440c2c63dfad7f71bc255d14087c0767ab85 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 11:35:49 +0200 Subject: [PATCH 04/28] Raise an CanOperationError when an exception occurs in the send method. The method returns True on success and False on timeout, if the user wanted an timeout. --- can/interfaces/ixxat/canlib.py | 30 +++++++++++++++++++++++------- test/test_interface_ixxat.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 7fae57f97..52637b12d 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -115,6 +115,8 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value + #print(hex(result), function) + if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) elif result == constants.VCI_E_RXQUEUE_EMPTY: @@ -318,7 +320,6 @@ def __init__(self, channel, can_filters=None, **kwargs): self._channel_handle = HANDLE() self._channel_capabilities = structures.CANCAPABILITIES() self._message = structures.CANMSG() - self._payload = (ctypes.c_byte * 8)() # Search for supplied device if UniqueHardwareId is None: @@ -456,7 +457,6 @@ 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]))) 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]))) - # TODO report error with is_error_frame is set to true elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: pass else: @@ -487,7 +487,12 @@ def _recv_internal(self, timeout): return rx_msg, True def send(self, msg, timeout=None): - + """ + Sends a message on the bus. The interface may buffer the message. + returns True on success or when timeout is None + returns False on timeout (when timeout is not None) + raises CanOperationError + """ # This system is not designed to be very efficient message = structures.CANMSG() message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA @@ -500,10 +505,21 @@ def send(self, msg, timeout=None): adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) ctypes.memmove(message.abData, adapter, len(msg.data)) - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) + try: + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + except VCITimeout: + # if the user wanted an timeout, the timeout in the library is probably no error + if timeout: + return False + else: + raise(CanOperationError()) + except: + raise(CanOperationError()) + + return True def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 76c77052d..fcaf58536 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -5,13 +5,14 @@ """ import unittest - import can -from can import CanInitializationError - +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError -class InterfaceIxxatTestCase(unittest.TestCase): +class SoftwareTestCase(unittest.TestCase): + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ def setUp(self): pass @@ -30,10 +31,33 @@ def test_bus_creation(self): # txFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) - + + +class HardwareTestCase(unittest.TestCase): + """ + Test cases that rely on an existing/connected hardware. + """ + def setUp(self): + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except: + raise(unittest.SkipTest()) + + def tearDown(self): + pass + + def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(CanInitializationError): bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + + def test_send_after_shutdown(self): + bus = can.Bus(interface = 'ixxat', channel = 0) + msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus.shutdown() + with self.assertRaises(CanOperationError): + bus.send(msg) + if __name__ == '__main__': unittest.main() \ No newline at end of file From 07326ef63f49e5b7b7a1b6f59ebed3d502e34930 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 11:48:19 +0200 Subject: [PATCH 05/28] Change intentions to 4 spaces to match remote. --- can/interfaces/ixxat/canlib.py | 1042 ++++++++++++++-------------- can/interfaces/ixxat/exceptions.py | 16 +- test/test_interface_ixxat.py | 90 +-- 3 files changed, 574 insertions(+), 574 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 52637b12d..bfa518830 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -7,10 +7,10 @@ Copyright (C) 2019 Marcel Kanter TODO: We could implement this interface such that setting other filters - could work when the initial filters were set to zero using the - software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! - See also the NICAN interface. + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. """ @@ -34,10 +34,10 @@ log = logging.getLogger('can.ixxat') try: - # since Python 3.3 - from time import perf_counter as _timer_function + # since Python 3.3 + from time import perf_counter as _timer_function except ImportError: - from time import clock as _timer_function + from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -45,547 +45,547 @@ # main ctypes instance _canlib = None if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) elif sys.platform == "cygwin": - try: - _canlib = CLibrary("vcinpl.dll") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) else: - # Will not work on other systems, but have it importable anyway for - # tests/sphinx - log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """ Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string - """ - #TODO: make sure we don't generate another exception - return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) + """ Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :param arguments: + Arbitrary arguments tuple + :return: + Formatted string + """ + #TODO: make sure we don't generate another exception + return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) def __vciFormatError(library_instance, function, HRESULT): - """ Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string - """ - buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + """ Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) def __check_status(result, function, arguments): - """ - Check the result of a vcinpl function call and raise appropriate exception - in case of an error. Used as errcheck function when mapping C functions - with ctypes. - :param result: - Function call numeric result - :param callable function: - Called function - :param arguments: - Arbitrary arguments tuple - :raise: - :class:VCITimeout - :class:VCIRxQueueEmptyError - :class:StopIteration - :class:VCIError - """ - if isinstance(result, int): - # Real return value is an unsigned long - result = ctypes.c_ulong(result).value - - #print(hex(result), function) - - if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) - elif result == constants.VCI_E_RXQUEUE_EMPTY: - raise VCIRxQueueEmptyError() - elif result == constants.VCI_E_NO_MORE_ITEMS: - raise StopIteration() - elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus - elif result != constants.VCI_OK: - raise VCIError(vciFormatError(function, result)) - - return result + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param arguments: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if isinstance(result, int): + # Real return value is an unsigned long + result = ctypes.c_ulong(result).value + + #print(hex(result), function) + + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result try: - # Map all required symbols and initialize library - #HRESULT VCIAPI vciInitialize ( void ); - _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - - #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) - # Hack to have vciFormatError as a free function - vciFormatError = functools.partial(__vciFormatError, _canlib) - - # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) - - # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) - # HRESULT vciDeviceClose( HANDLE hDevice ) - _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - - # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - - #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) - #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) - #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) - #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) - #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - _canlib.vciInitialize() + # Map all required symbols and initialize library + #HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) + + #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); + _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + + #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) + #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) + #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) + #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) + #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + _canlib.vciInitialize() except AttributeError: - # In case _canlib == None meaning we're not on win32/no lib found - pass + # In case _canlib == None meaning we're not on win32/no lib found + pass except Exception as e: - log.warning("Could not initialize IXXAT VCI library: %s", e) + log.warning("Could not initialize IXXAT VCI library: %s", e) # --------------------------------------------------------------------------- CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } #---------------------------------------------------------------------------- class IXXATBus(BusABC): - """The CAN Bus implemented for the IXXAT interface. - - .. warning:: - - This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. - - """ - - CHANNEL_BITRATES = { - 0: { - 10000: constants.CAN_BT0_10KB, - 20000: constants.CAN_BT0_20KB, - 50000: constants.CAN_BT0_50KB, - 100000: constants.CAN_BT0_100KB, - 125000: constants.CAN_BT0_125KB, - 250000: constants.CAN_BT0_250KB, - 500000: constants.CAN_BT0_500KB, - 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB - }, - 1: { - 10000: constants.CAN_BT1_10KB, - 20000: constants.CAN_BT1_20KB, - 50000: constants.CAN_BT1_50KB, - 100000: constants.CAN_BT1_100KB, - 125000: constants.CAN_BT1_125KB, - 250000: constants.CAN_BT1_250KB, - 500000: constants.CAN_BT1_500KB, - 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } - } - - def __init__(self, channel, can_filters=None, **kwargs): - """ - :param int channel: - The Channel id to create this bus with. - - :param list can_filters: - See :meth:`can.BusABC.set_filters`. - - :param bool receive_own_messages: - Enable self-reception of sent messages. - - :param int UniqueHardwareId: - UniqueHardwareId to connect (optional, will use the first found if not supplied) - - :param int bitrate: - Channel bitrate in bit/s - """ - if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") - log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", kwargs) - # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) - # Usually comes as a string from the config file - channel = int(channel) - - if (bitrate not in self.CHANNEL_BITRATES[0]): - raise ValueError("Invalid bitrate {}".format(bitrate)) - - if rxFifoSize <= 0: - raise ValueError("rxFifoSize must be > 0") - - if txFifoSize <= 0: - raise ValueError("txFifoSize must be > 0") - - if channel < 0: - raise ValueError("channel number must be >= 0") - - self._device_handle = HANDLE() - self._device_info = structures.VCIDEVICEINFO() - self._control_handle = HANDLE() - self._channel_handle = HANDLE() - self._channel_capabilities = structures.CANCAPABILITIES() - self._message = structures.CANMSG() - - # Search for supplied device - if UniqueHardwareId is None: - log.info("Searching for first available device") - else: - log.info("Searching for unique HW ID %s", UniqueHardwareId) - _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) - except StopIteration: - if (UniqueHardwareId is None): - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") - else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) - else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): - break - else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) - _canlib.vciEnumDeviceClose(self._device_handle) - - try: - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) - except: - raise(CanInitializationError("Could not open device.")) - - log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) - - log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) - - try: - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) - # Signal TX/RX events when at least one frame has been handled - except: - raise(CanInitializationError("Could not open and initialize channel.")) - - _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) - _canlib.canChannelActivate(self._channel_handle, constants.TRUE) - - log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) - _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) - _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) - - # With receive messages, this field contains the relative reception time of - # the message in ticks. The resolution of a tick can be calculated from the fields - # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: - # frequency [1/s] = dwClockFreq / dwTscDivisor - # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) - - # Setup filters before starting the channel - if can_filters: - log.info("The IXXAT VCI backend is filtering messages") - # Disable every message coming in - for extended in (0, 1): - _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) - for can_filter in can_filters: - # Whitelist - code = int(can_filter['can_id']) - mask = int(can_filter['can_mask']) - extended = can_filter.get('extended', False) - _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) - log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) - - # Start the CAN controller. Messages will be forwarded to the channel - _canlib.canControlStart(self._control_handle, constants.TRUE) - - # For cyclic transmit list. Set when .send_periodic() is first called - self._scheduler = None - self._scheduler_resolution = None - self.channel = channel - - # Usually you get back 3 messages like "CAN initialized" ecc... - # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): - try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - break - - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) - - def _inWaiting(self): - try: - _canlib.canChannelWaitRxEvent(self._channel_handle, 0) - except VCITimeout: - return 0 - else: - return 1 - - def flush_tx_buffer(self): - """ Flushes the transmit buffer on the IXXAT """ - # TODO #64: no timeout? - _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) - - def _recv_internal(self, timeout): - """ Read a message from IXXAT device. """ - - # TODO: handling CAN error messages? - data_received = False - - if 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 - 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 = _timer_function() - - while True: - try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the _timer_function() - 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 - 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]))) - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: - pass - else: - log.warn("Unexpected message info type") - raise(CanBackEndError()) - - if t0 is not None: - remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) - if remaining_ms < 0: - break - - if not data_received: - # Timed out / can message type is not DATA - return None, True - - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 - rx_msg = Message( - timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s - is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, - arbitration_id=self._message.dwMsgId, - dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], - channel=self.channel - ) - - return rx_msg, True - - def send(self, msg, timeout=None): - """ - Sends a message on the bus. The interface may buffer the message. - returns True on success or when timeout is None - returns False on timeout (when timeout is not None) - raises CanOperationError - """ - # This system is not designed to be very efficient - message = structures.CANMSG() - message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 - message.dwMsgId = msg.arbitration_id - if msg.dlc: - message.uMsgInfo.Bits.dlc = msg.dlc - adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) - ctypes.memmove(message.abData, adapter, len(msg.data)) - - try: - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - except VCITimeout: - # if the user wanted an timeout, the timeout in the library is probably no error - if timeout: - return False - else: - raise(CanOperationError()) - except: - raise(CanOperationError()) - - return True - - def _send_periodic_internal(self, msg, period, duration=None): - """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) - - def shutdown(self): - if self._scheduler is not None: - _canlib.canSchedulerClose(self._scheduler) - _canlib.canChannelClose(self._channel_handle) - _canlib.canControlStart(self._control_handle, constants.FALSE) - _canlib.canControlClose(self._control_handle) - _canlib.vciDeviceClose(self._device_handle) - - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + CHANNEL_BITRATES = { + 0: { + 10000: constants.CAN_BT0_10KB, + 20000: constants.CAN_BT0_20KB, + 50000: constants.CAN_BT0_50KB, + 100000: constants.CAN_BT0_100KB, + 125000: constants.CAN_BT0_125KB, + 250000: constants.CAN_BT0_250KB, + 500000: constants.CAN_BT0_500KB, + 800000: constants.CAN_BT0_800KB, + 1000000: constants.CAN_BT0_1000KB + }, + 1: { + 10000: constants.CAN_BT1_10KB, + 20000: constants.CAN_BT1_20KB, + 50000: constants.CAN_BT1_50KB, + 100000: constants.CAN_BT1_100KB, + 125000: constants.CAN_BT1_125KB, + 250000: constants.CAN_BT1_250KB, + 500000: constants.CAN_BT1_500KB, + 800000: constants.CAN_BT1_800KB, + 1000000: constants.CAN_BT1_1000KB + } + } + + def __init__(self, channel, can_filters=None, **kwargs): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + :param bool receive_own_messages: + Enable self-reception of sent messages. + + :param int UniqueHardwareId: + UniqueHardwareId to connect (optional, will use the first found if not supplied) + + :param int bitrate: + Channel bitrate in bit/s + """ + if _canlib is None: + raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + log.info("CAN Filters: %s", can_filters) + log.info("Got configuration of: %s", kwargs) + # Configuration options + bitrate = kwargs.get('bitrate', 500000) + UniqueHardwareId = kwargs.get('UniqueHardwareId', None) + rxFifoSize = kwargs.get('rxFifoSize', 16) + txFifoSize = kwargs.get('txFifoSize', 16) + self._receive_own_messages = kwargs.get('receive_own_messages', False) + # Usually comes as a string from the config file + channel = int(channel) + + if (bitrate not in self.CHANNEL_BITRATES[0]): + raise ValueError("Invalid bitrate {}".format(bitrate)) + + if rxFifoSize <= 0: + raise ValueError("rxFifoSize must be > 0") + + if txFifoSize <= 0: + raise ValueError("txFifoSize must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES() + self._message = structures.CANMSG() + + # Search for supplied device + if UniqueHardwareId is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", UniqueHardwareId) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + except StopIteration: + if (UniqueHardwareId is None): + raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + else: + raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + else: + if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + break + else: + log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + except: + raise(CanInitializationError("Could not open device.")) + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) + + try: + _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + # Signal TX/RX events when at least one frame has been handled + except: + raise(CanInitializationError("Could not open and initialize channel.")) + + _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + log.info("Initializing control %d bitrate %d", channel, bitrate) + _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) + _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) + _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + # We explicitly cast to float for Python 2.x users + self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) + for can_filter in can_filters: + # Whitelist + code = int(can_filter['can_id']) + mask = int(can_filter['can_mask']) + extended = can_filter.get('extended', False) + _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for i in range(rxFifoSize): + try: + _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """ Flushes the transmit buffer on the IXXAT """ + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """ Read a message from IXXAT device. """ + + # TODO: handling CAN error messages? + data_received = False + + if 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 + 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 = _timer_function() + + while True: + try: + _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the _timer_function() + 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 + 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]))) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warn("Unexpected message info type") + raise(CanBackEndError()) + + if t0 is not None: + remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s + is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, + is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + arbitration_id=self._message.dwMsgId, + dlc=self._message.uMsgInfo.Bits.dlc, + data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], + channel=self.channel + ) + + return rx_msg, True + + def send(self, msg, timeout=None): + """ + Sends a message on the bus. The interface may buffer the message. + returns True on success or when timeout is None + returns False on timeout (when timeout is not None) + raises CanOperationError + """ + # This system is not designed to be very efficient + message = structures.CANMSG() + message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: + message.uMsgInfo.Bits.dlc = msg.dlc + adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) + ctypes.memmove(message.abData, adapter, len(msg.data)) + + try: + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + except VCITimeout: + # if the user wanted an timeout, the timeout in the library is probably no error + if timeout: + return False + else: + raise(CanOperationError()) + except: + raise(CanOperationError()) + + return True + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. + """ + if self.__set_filters_has_been_called: + log.warn("using filters is not supported like this, see note on IXXATBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): - """A message in the cyclic transmit list.""" - - def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) - self._scheduler = scheduler - self._index = None - self._count = int(duration / period) if duration else 0 - - self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = msg.arbitration_id - self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.data): - self._msg.abData[i] = b - self.start() - - def start(self): - """Start transmitting message (add to list if needed).""" - if self._index is None: - self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) - _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) - - def pause(self): - """Pause transmitting message (keep it in the list).""" - _canlib.canSchedulerStopMessage(self._scheduler, self._index) - - def stop(self): - """Stop transmitting message (remove from list).""" - # Remove it completely instead of just stopping it to avoid filling up - # the list with permanently stopped messages - _canlib.canSchedulerRemMessage(self._scheduler, self._index) - self._index = None + RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msg, period, duration, resolution): + super(CyclicSendTask, self).__init__(msg, period, duration) + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = msg.arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = msg.dlc + for i, b in enumerate(msg.data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 03c3b7167..c9a83dd4a 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -13,20 +13,20 @@ class VCITimeout(CanError): - """ Wraps the VCI_E_TIMEOUT error """ - pass + """ Wraps the VCI_E_TIMEOUT error """ + pass class VCIError(CanError): - """ Try to display errors that occur within the wrapped C library nicely. """ - pass + """ Try to display errors that occur within the wrapped C library nicely. """ + pass class VCIRxQueueEmptyError(VCIError): - """ Wraps the VCI_E_RXQUEUE_EMPTY error """ - def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): + super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") class VCIDeviceNotFoundError(CanInitializationError): - pass + pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index fcaf58536..2299b2559 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -10,54 +10,54 @@ from can import CanError, CanBackEndError, CanInitializationError, CanOperationError class SoftwareTestCase(unittest.TestCase): - """ - Test cases that test the software only and do not rely on an existing/connected hardware. - """ - def setUp(self): - pass - - def tearDown(self): - pass - - def test_bus_creation(self): - # channel must be >= 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = -1) - - # rxFifoSize must be > 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) - - # txFifoSize must be > 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ + def setUp(self): + pass + + def tearDown(self): + pass + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = -1) + + # rxFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + + # txFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) class HardwareTestCase(unittest.TestCase): - """ - Test cases that rely on an existing/connected hardware. - """ - def setUp(self): - try: - bus = can.Bus(interface = 'ixxat', channel = 0) - except: - raise(unittest.SkipTest()) - - def tearDown(self): - pass - - def test_bus_creation(self): - # non-existent channel -> use arbitrary high value - with self.assertRaises(CanInitializationError): - bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) - - def test_send_after_shutdown(self): - bus = can.Bus(interface = 'ixxat', channel = 0) - msg = can.Message(arbitration_id = 0x3FF, dlc = 0) - bus.shutdown() - with self.assertRaises(CanOperationError): - bus.send(msg) + """ + Test cases that rely on an existing/connected hardware. + """ + def setUp(self): + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except: + raise(unittest.SkipTest()) + + def tearDown(self): + pass + + def test_bus_creation(self): + # non-existent channel -> use arbitrary high value + with self.assertRaises(CanInitializationError): + bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + + def test_send_after_shutdown(self): + bus = can.Bus(interface = 'ixxat', channel = 0) + msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus.shutdown() + with self.assertRaises(CanOperationError): + bus.send(msg) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From d5be879f5e64a9b269884b71cedae0491091c09f Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 21:17:11 +0200 Subject: [PATCH 06/28] Skip the test if there is an ImportError --- test/test_interface_ixxat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 2299b2559..15931856a 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -14,7 +14,10 @@ class SoftwareTestCase(unittest.TestCase): Test cases that test the software only and do not rely on an existing/connected hardware. """ def setUp(self): - pass + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except ImportError: + raise(unittest.SkipTest()) def tearDown(self): pass @@ -40,7 +43,7 @@ class HardwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) - except: + except ImportError: raise(unittest.SkipTest()) def tearDown(self): From e9c709639878a6794936dd3a23d173d6cedd85b2 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 21:25:45 +0200 Subject: [PATCH 07/28] Merged in suggested changes from pull request. --- can/interfaces/ixxat/canlib.py | 10 +++++----- test/test_interface_ixxat.py | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index bfa518830..61dae6871 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -23,7 +23,7 @@ from can import BusABC, Message from can import CanError, CanBackEndError, CanInitializationError, CanOperationError -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) +from can.broadcastmanager import LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures @@ -345,7 +345,7 @@ def __init__(self, channel, can_filters=None, **kwargs): try: _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) except: - raise(CanInitializationError("Could not open device.")) + raise CanInitializationError("Could not open device.") log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -355,7 +355,7 @@ def __init__(self, channel, can_filters=None, **kwargs): _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) # Signal TX/RX events when at least one frame has been handled except: - raise(CanInitializationError("Could not open and initialize channel.")) + raise CanInitializationError("Could not open and initialize channel.") _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) _canlib.canChannelActivate(self._channel_handle, constants.TRUE) @@ -515,9 +515,9 @@ def send(self, msg, timeout=None): if timeout: return False else: - raise(CanOperationError()) + raise CanOperationError("Timeout in library call.") except: - raise(CanOperationError()) + raise CanOperationError("Send failed.") return True diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 15931856a..a3ab494fb 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -16,6 +16,7 @@ class SoftwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) + bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -43,6 +44,7 @@ class HardwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) + bus.shutdown() except ImportError: raise(unittest.SkipTest()) From 5d40159aeba7e31926ac103dc85d07eb4a51e88b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Sun, 12 May 2019 08:37:57 +0200 Subject: [PATCH 08/28] Introduced CanTimeoutError. Win32 and cygwin will both load "vcimpl.dll". Added comments. --- can/exceptions.py | 14 +++++---- can/interfaces/ixxat/canlib.py | 46 ++++++++++-------------------- can/interfaces/ixxat/exceptions.py | 2 +- test/test_interface_ixxat.py | 18 ++++++------ 4 files changed, 33 insertions(+), 47 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index d43a27648..a91fc7a1b 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,7 +1,5 @@ """ Exception classes. - -Copyright (c) Marcel Kanter """ class CanError(Exception): @@ -22,12 +20,18 @@ class CanInitializationError(CanError): """ Indicates an error related to the initialization. Examples for situations when this exception may occur: - Try to open a non-existent device and/or channel - - Try to use a invalid setting, which is ok by value, but not ok for the interface + - Try to use an invalid setting, which is ok by value, but not ok for the interface """ pass class CanOperationError(CanError): - """ Indicates an error while operation. + """ Indicates an error while in operation. + """ + pass + + +class CanTimeoutError(CanError): + """ Indicates a timeout of an operation. """ - pass \ No newline at end of file + pass diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 61dae6871..47d88a85b 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,11 +1,6 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems -Copyright (C) 2016 Giuseppe Corbelli -Copyright (C) 2019 Marcel Kanter - TODO: We could implement this interface such that setting other filters could work when the initial filters were set to zero using the software fallback. Or could the software filters even be changed @@ -44,12 +39,8 @@ # main ctypes instance _canlib = None -if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) -elif sys.platform == "cygwin": +# TODO: Use ECI driver for linux +if sys.platform == "win32" or sys.platform == "cygwin": try: _canlib = CLibrary("vcinpl.dll") except Exception as e: @@ -489,9 +480,14 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): """ Sends a message on the bus. The interface may buffer the message. - returns True on success or when timeout is None - returns False on timeout (when timeout is not None) - raises CanOperationError + + :param can.Message msg: + The message to send. + :param float timeout: + Timeout after some time. + :raise: + :class:CanTimeoutError + :class:CanOperationError """ # This system is not designed to be very efficient message = structures.CANMSG() @@ -505,21 +501,10 @@ def send(self, msg, timeout=None): adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) ctypes.memmove(message.abData, adapter, len(msg.data)) - try: - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - except VCITimeout: - # if the user wanted an timeout, the timeout in the library is probably no error - if timeout: - return False - else: - raise CanOperationError("Timeout in library call.") - except: - raise CanOperationError("Send failed.") - - return True + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" @@ -551,8 +536,7 @@ def set_filters(self, can_filers=None): self.__set_filters_has_been_called = True -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" def __init__(self, scheduler, msg, period, duration, resolution): diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index c9a83dd4a..17d51df40 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -12,7 +12,7 @@ __all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] -class VCITimeout(CanError): +class VCITimeout(CanTimeoutError): """ Wraps the VCI_E_TIMEOUT error """ pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index a3ab494fb..e57eab368 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,7 +1,5 @@ """ Unittest for ixxat interface. - -Copyright (C) 2019 Marcel Kanter """ import unittest @@ -15,7 +13,7 @@ class SoftwareTestCase(unittest.TestCase): """ def setUp(self): try: - bus = can.Bus(interface = 'ixxat', channel = 0) + bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -26,15 +24,15 @@ def tearDown(self): def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = -1) + bus = can.Bus(interface="ixxat", channel=-1) # rxFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) # txFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) class HardwareTestCase(unittest.TestCase): @@ -43,7 +41,7 @@ class HardwareTestCase(unittest.TestCase): """ def setUp(self): try: - bus = can.Bus(interface = 'ixxat', channel = 0) + bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -54,11 +52,11 @@ def tearDown(self): def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(CanInitializationError): - bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + bus = can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): - bus = can.Bus(interface = 'ixxat', channel = 0) - msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus = can.Bus(interface="ixxat", channel=0) + msg = can.Message(arbitration_id=0x3FF, dlc=0) bus.shutdown() with self.assertRaises(CanOperationError): bus.send(msg) From e29c162b7388d97cb03a735576e2c3362cdc6c50 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 May 2019 12:07:59 +1000 Subject: [PATCH 09/28] Change version to 4.0.0-dev --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index d377652d0..ded1c2f94 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0-exception-handling" +__version__ = "4.0.0-dev" log = logging.getLogger('can') From 543163d660eed87edbdc50f026e7fed349e9d22b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Sun, 19 May 2019 07:38:02 +0200 Subject: [PATCH 10/28] Fix imports Change version to 4.0.0.dev0 Fix classifiers in setup.py, it should be a list. --- can/__init__.py | 4 ++-- can/interfaces/ixxat/canlib.py | 2 +- can/interfaces/ixxat/exceptions.py | 6 ++---- doc/api.rst | 8 +++++++- setup.py | 4 ++-- test/test_interface_ixxat.py | 8 +++++--- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index d377652d0..25370da36 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,13 +8,13 @@ import logging -__version__ = "3.2.0-exception-handling" +__version__ = "4.0.0.dev0" log = logging.getLogger('can') rc = dict() -from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError +from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 47d88a85b..8ef3ae028 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -17,7 +17,7 @@ import sys from can import BusABC, Message -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError +from can.exceptions import * from can.broadcastmanager import LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 17d51df40..448c6b162 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -7,9 +7,7 @@ Copyright (C) 2019 Marcel Kanter """ -from can import CanError, CanInitializationError - -__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError class VCITimeout(CanTimeoutError): @@ -17,7 +15,7 @@ class VCITimeout(CanTimeoutError): pass -class VCIError(CanError): +class VCIError(CanOperationError): """ Try to display errors that occur within the wrapped C library nicely. """ pass diff --git a/doc/api.rst b/doc/api.rst index 640f61e2d..7997171be 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -40,4 +40,10 @@ The Notifier object is used as a message distributor for a bus. Errors ------ -.. autoclass:: can.CanError +| CanError +| CanBackendError +| CanInitializationError +| CanOperationError +| CanTimeoutError + +.. automodule:: can.exceptions diff --git a/setup.py b/setup.py index 87c9d489c..c600b7215 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ url="https://github.com/hardbyte/python-can", description="Controller Area Network interface module for Python", long_description=long_description, - classifiers=( + classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", "Programming Language :: Python :: 2.7", @@ -74,7 +74,7 @@ "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", "Topic :: Utilities" - ), + ], # Code version=version, diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index e57eab368..44a1b61bb 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,11 +1,13 @@ """ Unittest for ixxat interface. + +Run only this test: +python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py" """ import unittest import can -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError class SoftwareTestCase(unittest.TestCase): """ @@ -51,14 +53,14 @@ def tearDown(self): def test_bus_creation(self): # non-existent channel -> use arbitrary high value - with self.assertRaises(CanInitializationError): + with self.assertRaises(can.CanInitializationError): bus = can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): bus = can.Bus(interface="ixxat", channel=0) msg = can.Message(arbitration_id=0x3FF, dlc=0) bus.shutdown() - with self.assertRaises(CanOperationError): + with self.assertRaises(can.CanOperationError): bus.send(msg) From 3cf73c8c0ff311f83204409224e3adfedcd9039d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 May 2019 15:47:50 +1000 Subject: [PATCH 11/28] Update __init__.py --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index ded1c2f94..f8851be5f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "4.0.0-dev" +__version__ = "4.0.0-dev0" log = logging.getLogger('can') From 21f3ae7ac1e7408abcac64186984d03434be141b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 22 Apr 2021 13:53:52 +0000 Subject: [PATCH 12/28] Format code with black --- can/__init__.py | 8 +++++- can/exceptions.py | 43 ++++++++++++++++-------------- can/interfaces/ixxat/canlib.py | 20 +++++++------- can/interfaces/ixxat/exceptions.py | 9 ++++++- test/test_interface_ixxat.py | 22 ++++++++------- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index a1996d39e..2ce879fa8 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -16,7 +16,13 @@ from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader -from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError +from .exceptions import ( + CanError, + CanBackEndError, + CanInitializationError, + CanOperationError, + CanTimeoutError, +) from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader diff --git a/can/exceptions.py b/can/exceptions.py index a91fc7a1b..e7a301d59 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -2,36 +2,39 @@ Exception classes. """ + class CanError(Exception): - """ Base class for all can related exceptions. - """ - pass + """Base class for all can related exceptions.""" + + pass class CanBackEndError(CanError): - """ Indicates an error related to the backend (e.g. driver/OS/library) - Examples: - - A call to a library function results in an unexpected return value - """ - pass + """Indicates an error related to the backend (e.g. driver/OS/library) + Examples: + - A call to a library function results in an unexpected return value + """ + + pass class CanInitializationError(CanError): - """ Indicates an error related to the initialization. - Examples for situations when this exception may occur: - - 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 - """ - pass + """Indicates an error related to the initialization. + Examples for situations when this exception may occur: + - 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 + """ + + pass class CanOperationError(CanError): - """ Indicates an error while in operation. - """ - pass + """Indicates an error while in operation.""" + + pass class CanTimeoutError(CanError): - """ Indicates a timeout of an operation. - """ - pass + """Indicates a timeout of an operation.""" + + pass diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 2f37d3a71..5523ad2cb 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -114,7 +114,7 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value - #print(hex(result), function) + # print(hex(result), function) if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) @@ -443,13 +443,13 @@ def __init__(self, channel, can_filters=None, **kwargs): if bitrate not in self.CHANNEL_BITRATES[0]: raise ValueError("Invalid bitrate {}".format(bitrate)) - + if rxFifoSize <= 0: raise ValueError("rxFifoSize must be > 0") - + if txFifoSize <= 0: raise ValueError("txFifoSize must be > 0") - + if channel < 0: raise ValueError("channel number must be >= 0") @@ -514,11 +514,11 @@ def __init__(self, channel, can_filters=None, **kwargs): try: _canlib.canChannelOpen( - self._device_handle, - channel, - constants.FALSE, - ctypes.byref(self._channel_handle), - ) + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) except: raise CanInitializationError("Could not open and initialize channel.") @@ -710,7 +710,7 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): """ Sends a message on the bus. The interface may buffer the message. - + :param can.Message msg: The message to send. :param float timeout: diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index f6e66b385..21e00a94f 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -5,7 +5,13 @@ Copyright (C) 2019 Marcel Kanter """ -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError +from can import ( + CanError, + CanBackEndError, + CanInitializationError, + CanOperationError, + CanTimeoutError, +) __all__ = [ "VCITimeout", @@ -15,6 +21,7 @@ "VCIDeviceNotFoundError", ] + class VCITimeout(CanTimeoutError): """ Wraps the VCI_E_TIMEOUT error """ diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 44a1b61bb..f9bb135df 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -13,25 +13,26 @@ class SoftwareTestCase(unittest.TestCase): """ Test cases that test the software only and do not rely on an existing/connected hardware. """ + def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise(unittest.SkipTest()) - + raise (unittest.SkipTest()) + def tearDown(self): pass - + def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=-1) - + # rxFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) - + # txFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) @@ -41,21 +42,22 @@ class HardwareTestCase(unittest.TestCase): """ Test cases that rely on an existing/connected hardware. """ + def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise(unittest.SkipTest()) - + raise (unittest.SkipTest()) + def tearDown(self): pass - + def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(can.CanInitializationError): bus = can.Bus(interface="ixxat", channel=0xFFFF) - + def test_send_after_shutdown(self): bus = can.Bus(interface="ixxat", channel=0) msg = can.Message(arbitration_id=0x3FF, dlc=0) @@ -64,5 +66,5 @@ def test_send_after_shutdown(self): bus.send(msg) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From cd06b779effbdb39b4628e1b901d05d7419f135c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:32:51 +0200 Subject: [PATCH 13/28] clean up --- can/exceptions.py | 22 +++++++++++----------- doc/api.rst | 8 ++------ doc/doc-requirements.txt | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index e7a301d59..c8f670e17 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,13 +1,21 @@ """ -Exception classes. +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) + +-- CanBackendError + +-- CanInitializationError + +-- CanOperationError + +-- CanTimeoutError + """ class CanError(Exception): """Base class for all can related exceptions.""" - pass - class CanBackEndError(CanError): """Indicates an error related to the backend (e.g. driver/OS/library) @@ -15,8 +23,6 @@ class CanBackEndError(CanError): - A call to a library function results in an unexpected return value """ - pass - class CanInitializationError(CanError): """Indicates an error related to the initialization. @@ -25,16 +31,10 @@ class CanInitializationError(CanError): - Try to use an invalid setting, which is ok by value, but not ok for the interface """ - pass - class CanOperationError(CanError): """Indicates an error while in operation.""" - pass - class CanTimeoutError(CanError): """Indicates a timeout of an operation.""" - - pass diff --git a/doc/api.rst b/doc/api.rst index 551d6c53e..8674334c4 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -41,10 +41,6 @@ The Notifier object is used as a message distributor for a bus. Notifier creates Errors ------ -| CanError -| CanBackendError -| CanInitializationError -| CanOperationError -| CanTimeoutError - .. automodule:: can.exceptions + :members: + :show-inheritance: diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index a63beee71..dead5e2e5 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,3 +1,3 @@ sphinx>=1.8.1 sphinxcontrib-programoutput -sphinx-autodoc-typehints==1.6.0 +sphinx-autodoc-typehints From 89d09ff4333068616a60c7af92e6b3e57d81be43 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:50:56 +0200 Subject: [PATCH 14/28] cleanup old unwanted changes --- can/interfaces/ixxat/canlib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 5523ad2cb..b7e9f25f1 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -13,6 +13,7 @@ import functools import logging import sys +from typing import Optional from can import BusABC, Message from can.exceptions import * @@ -114,8 +115,6 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value - # print(hex(result), function) - if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) elif result == constants.VCI_E_RXQUEUE_EMPTY: @@ -459,6 +458,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self._channel_handle = HANDLE() self._channel_capabilities = structures.CANCAPABILITIES() self._message = structures.CANMSG() + self._payload = (ctypes.c_byte * 8)() # Search for supplied device if UniqueHardwareId is None: @@ -707,13 +707,13 @@ def _recv_internal(self, timeout): return rx_msg, True - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """ Sends a message on the bus. The interface may buffer the message. - :param can.Message msg: + :param msg: The message to send. - :param float timeout: + :param timeout: Timeout after some time. :raise: :class:CanTimeoutError From bfbb0f1962207f0d890a6a250794c094078fa7c9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:59:57 +0200 Subject: [PATCH 15/28] improve docs --- can/exceptions.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index c8f670e17..cedad3ee8 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -10,31 +10,51 @@ +-- 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 be documented for the function at hand. """ class CanError(Exception): - """Base class for all can related exceptions.""" + """Base class for all CAN related exceptions.""" class CanBackEndError(CanError): - """Indicates an error related to the backend (e.g. driver/OS/library) - Examples: - - A call to a library function results in an unexpected return value + """Indicates an error related to the backend (e.g. driver/OS/library). + + Example scenarios: + - The driver is not present or has the wrong version + - The interface is unsupported on the current platform """ class CanInitializationError(CanError): - """Indicates an error related to the initialization. - Examples for situations when this exception may occur: - - 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 + """Indicates an error the occurred while initializing a :class:`can.Bus`. + + 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.""" + """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 + - Attempted to send an invalid message + - Cyclic redundancy check (CRC) failed + - Message remained unacknowledged + """ -class CanTimeoutError(CanError): - """Indicates a timeout of an operation.""" +class CanTimeoutError(CanError, TimeoutError): + """Indicates a timeout of an operation. + + Example scenarios: + - Some message could not be sent after the timeout elapsed + - No message was read within the given time + """ From daf66175719b84563ba79a72a2ef9e8520bd246f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 17:13:30 +0200 Subject: [PATCH 16/28] finalize ixxat interface --- can/exceptions.py | 2 +- can/interfaces/ixxat/canlib.py | 8 ++++---- can/interfaces/ixxat/exceptions.py | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index cedad3ee8..7bc8aeb29 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -52,7 +52,7 @@ class CanOperationError(CanError): class CanTimeoutError(CanError, TimeoutError): - """Indicates a timeout of an operation. + """Indicates the timeout of an operation. Example scenarios: - Some message could not be sent after the timeout elapsed diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index b7e9f25f1..503a40372 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -500,8 +500,8 @@ def __init__(self, channel, can_filters=None, **kwargs): ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle), ) - except: - raise CanInitializationError("Could not open device.") + except Exception as exception: + raise CanInitializationError(f"Could not open device: {exception}") log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -519,8 +519,8 @@ def __init__(self, channel, can_filters=None, **kwargs): constants.FALSE, ctypes.byref(self._channel_handle), ) - except: - raise CanInitializationError("Could not open and initialize channel.") + except Exception as exception: + raise CanInitializationError(f"Could not open and initialize channel: {exception}") # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 21e00a94f..3bc0e1111 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -6,8 +6,6 @@ """ from can import ( - CanError, - CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError, From a92928c4b59178261452c4120249807414a901a7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 15:20:50 +0000 Subject: [PATCH 17/28] Format code with black --- can/interfaces/ixxat/canlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 503a40372..9abef1210 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -520,7 +520,9 @@ def __init__(self, channel, can_filters=None, **kwargs): ctypes.byref(self._channel_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open and initialize channel: {exception}") + raise CanInitializationError( + f"Could not open and initialize channel: {exception}" + ) # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) From 8d0d20ae1660bf61ebd7e7fe4cf5e68c70346bd7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 18:02:29 +0200 Subject: [PATCH 18/28] finalize adding specific exceptions to the generic part of the library --- can/__init__.py | 2 +- can/broadcastmanager.py | 16 +++++++++++++++- can/bus.py | 25 +++++++++++++++---------- can/exceptions.py | 5 +++-- can/interface.py | 16 ++++++++++------ can/listener.py | 6 ++---- can/util.py | 9 +++++---- doc/api.rst | 3 +++ 8 files changed, 54 insertions(+), 28 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 2ce879fa8..d8bf17a5f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.0.0-dev0" +__version__ = "4.0.0-dev" log = logging.getLogger("can") diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6dd4f3fd7..457467d65 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -56,6 +56,8 @@ def __init__( :param messages: The messages to be sent periodically. :param period: The rate in seconds at which to send the messages. + + :raises ValueError: If the given messages are invalid """ messages = self._check_and_convert_messages(messages) @@ -74,7 +76,9 @@ def _check_and_convert_messages( Performs error checking to ensure that all Messages have the same arbitration ID and channel. - Should be called when the cyclic task is initialized + Should be called when the cyclic task is initialized. + + :raises ValueError: If the given messages are invalid """ if not isinstance(messages, (list, tuple)): if isinstance(messages, Message): @@ -115,6 +119,8 @@ def __init__( :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, period) self.duration = duration @@ -139,6 +145,8 @@ def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: cyclic messages hasn't changed. Should be called when modify_data is called in the cyclic task. + + :raises ValueError: If the given messages are invalid """ if len(self.messages) != len(messages): raise ValueError( @@ -163,6 +171,8 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: Note: The number of new cyclic messages to be sent must be equal to the original number of messages originally specified for this task. + + :raises ValueError: If the given messages are invalid """ messages = self._check_and_convert_messages(messages) self._check_modified_messages(messages) @@ -190,6 +200,8 @@ def __init__( :param count: :param initial_period: :param subsequent_period: + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, subsequent_period) self._channel = channel @@ -221,6 +233,8 @@ def __init__( error happened on a `bus` while sending `messages`, it shall return either ``True`` or ``False`` depending on desired behaviour of `ThreadBasedCyclicSendTask`. + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, period, duration) self.bus = bus diff --git a/can/bus.py b/can/bus.py index 246f45d19..231979407 100644 --- a/can/bus.py +++ b/can/bus.py @@ -31,7 +31,11 @@ class BusABC(metaclass=ABCMeta): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. - This class may be used as an iterator over the received messages. + This class may be used as an iterator over the received messages + and as a context manager for auto-closing the bus when done using it. + + Please refer to :ref:`errors` for possible exceptions that may be + thrown by certain operations on this bus. """ #: a string describing the underlying bus and/or channel @@ -60,6 +64,10 @@ def __init__( :param dict kwargs: Any backend dependent configurations are passed in this dictionary + + :raises ValueError: If parameters are out of range + :raises can.CanBackEndError: If the driver cannot be accessed + :raises can.CanInitializationError: If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) @@ -73,10 +81,9 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: :param timeout: seconds to wait for a message or None to wait indefinitely - :return: - None on timeout or a :class:`Message` object. - :raises can.CanError: - if an error occurred while reading + :return: ``None`` on timeout or a :class:`Message` object. + + :raises can.CanOperationError: If an error occurred while reading """ start = time() time_left = timeout @@ -141,8 +148,7 @@ def _recv_internal( 2. a bool that is True if message filtering has already been done and else False - :raises can.CanError: - if an error occurred while reading + :raises can.CanOperationError: If an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` implementation (legacy implementation) @@ -165,8 +171,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Might not be supported by all interfaces. None blocks indefinitely. - :raises can.CanError: - if the message could not be sent + :raises can.CanOperationError: If an error occurred while sending """ raise NotImplementedError("Trying to write to a readonly bus?") @@ -336,7 +341,7 @@ def set_filters( messages are matched. Calling without passing any filters will reset the applied - filters to `None`. + filters to ``None``. :param filters: A iterable of dictionaries each containing a "can_id", diff --git a/can/exceptions.py b/can/exceptions.py index 7bc8aeb29..3a279a449 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -24,13 +24,14 @@ class CanBackEndError(CanError): """Indicates an error related to the backend (e.g. driver/OS/library). Example scenarios: - - The driver is not present or has the wrong version + - The interface does not exist - The interface is unsupported on the current platform + - The driver is not present or has the wrong version """ class CanInitializationError(CanError): - """Indicates an error the occurred while initializing a :class:`can.Bus`. + """Indicates an error the occurred while initializing a :class:`can.BusABC`. Example scenarios: - Try to open a non-existent device and/or channel diff --git a/can/interface.py b/can/interface.py index 2d1ad0891..5a4d7caa7 100644 --- a/can/interface.py +++ b/can/interface.py @@ -10,6 +10,7 @@ from .bus import BusABC from .util import load_config from .interfaces import BACKENDS +from .exceptions import CanBackEndError log = logging.getLogger("can.interface") log_autodetect = log.getChild("detect_available_configs") @@ -22,7 +23,7 @@ def _get_class_for_interface(interface): :raises: NotImplementedError if the interface is not known :raises: - ImportError if there was a problem while importing the + CanBackEndError if there was a problem while importing the interface or the bus class within that """ # Find the correct backend @@ -37,7 +38,7 @@ def _get_class_for_interface(interface): try: module = importlib.import_module(module_name) except Exception as e: - raise ImportError( + raise CanBackEndError( "Cannot import module {} for CAN interface '{}': {}".format( module_name, interface, e ) @@ -47,7 +48,7 @@ def _get_class_for_interface(interface): try: bus_class = getattr(module, class_name) except Exception as e: - raise ImportError( + raise CanBackEndError( "Cannot import class {} from module {} for CAN interface '{}': {}".format( class_name, module_name, interface, e ) @@ -79,8 +80,11 @@ def __new__(cls, channel=None, *args, **kwargs): Should contain an ``interface`` key with a valid interface name. If not, it is completed using :meth:`can.util.load_config`. - :raises: NotImplementedError - if the ``interface`` isn't recognized + :raises: can.CanBackEndError + if the ``interface`` isn't recognized or cannot be loaded + + :raises: can.CanInitializationError + if the bus cannot be instantiated :raises: ValueError if the ``channel`` could not be determined @@ -148,7 +152,7 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) - except ImportError: + except CanBackEndError: log_autodetect.debug( 'interface "%s" can not be loaded for detection of available configurations', interface, diff --git a/can/listener.py b/can/listener.py index 2695e80c5..2cd643d44 100644 --- a/can/listener.py +++ b/can/listener.py @@ -41,7 +41,6 @@ def on_message_received(self, msg: Message): """This method is called to handle the given message. :param msg: the delivered message - """ def __call__(self, msg: Message): @@ -65,7 +64,6 @@ def stop(self): class RedirectReader(Listener): """ A RedirectReader sends all received messages to another Bus. - """ def __init__(self, bus: BusABC): @@ -86,13 +84,13 @@ class BufferedReader(Listener): Putting in messages after :meth:`~can.BufferedReader.stop` has been called will raise an exception, see :meth:`~can.BufferedReader.on_message_received`. - :attr bool is_stopped: ``True`` if the reader has been stopped + :attr is_stopped: ``True`` if the reader has been stopped """ def __init__(self): # set to "infinite" size self.buffer = SimpleQueue() - self.is_stopped = False + self.is_stopped: bool = False def on_message_received(self, msg: Message): """Append a message to the buffer. diff --git a/can/util.py b/can/util.py index 07fa1986a..56b1ca22a 100644 --- a/can/util.py +++ b/can/util.py @@ -15,8 +15,9 @@ from configparser import ConfigParser import can -from can.interfaces import VALID_INTERFACES -from can import typechecking +from .interfaces import VALID_INTERFACES +from . import typechecking +from .exceptions import CanBackEndError log = logging.getLogger("can.util") @@ -148,7 +149,7 @@ def load_config( All unused values are passed from ``config`` over to this. :raises: - NotImplementedError if the ``interface`` isn't recognized + CanBackEndError if the ``interface`` isn't recognized """ # start with an empty dict to apply filtering to all sources @@ -190,7 +191,7 @@ def load_config( config[key] = None if config["interface"] not in VALID_INTERFACES: - raise NotImplementedError(f'Invalid CAN Bus Type "{config["interface"]}"') + raise CanBackEndError(f'Invalid CAN Bus Type "{config["interface"]}"') if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) diff --git a/doc/api.rst b/doc/api.rst index 8674334c4..011553b1b 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -38,6 +38,9 @@ The Notifier object is used as a message distributor for a bus. Notifier creates .. autoclass:: can.Notifier :members: + +.. _errors: + Errors ------ From 65d5bd2c23ddcb7aadb42c9a852bef9d57edd235 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:29:46 +0200 Subject: [PATCH 19/28] clarify CanOperationError --- can/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 3a279a449..51b8dcced 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -46,9 +46,9 @@ class CanOperationError(CanError): Example scenarios: - A call to a library function results in an unexpected return value - An invalid message was received - - Attempted to send an invalid message + - The driver rejected a message that was meant to be sent - Cyclic redundancy check (CRC) failed - - Message remained unacknowledged + - A message remained unacknowledged """ From bcf75cb5317ee8e5de2023b1946749f13f7b0963 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:47:38 +0200 Subject: [PATCH 20/28] add error code to CanError --- can/exceptions.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index 51b8dcced..886635c44 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -15,9 +15,38 @@ :class:`ValueError`. This should be documented for the function at hand. """ +from typing import Optional + class CanError(Exception): - """Base class for all CAN related exceptions.""" + """Base class for all CAN related exceptions. + + If specified, the error code is automatically prepended to the message: + + >>> # With an error code: + >>> error = CanError(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, *args, **kwargs + ) -> None: + self.error_code = error_code + super().__init__( + message if error_code is None else f"{message} [Error Code {error_code}]" + ) class CanBackEndError(CanError): From 4e0ae779a01197ed94f492c04c2f0ead4e95412e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:51:52 +0200 Subject: [PATCH 21/28] improve docs --- can/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 886635c44..55c4b0d82 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -23,8 +23,8 @@ class CanError(Exception): If specified, the error code is automatically prepended to the message: - >>> # With an error code: - >>> error = CanError(message="Failed to do the thing", error_code=42) + >>> # 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]' >>> From 48b2acf7560ec280a8fdfc3fb3d318ceef0b4831 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:57:58 +0200 Subject: [PATCH 22/28] simplify IXXAT interface test --- test/test_interface_ixxat.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index f9bb135df..9e0cf550e 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -19,23 +19,20 @@ def setUp(self): bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise (unittest.SkipTest()) - - def tearDown(self): - pass + raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=-1) + can.Bus(interface="ixxat", channel=-1) # rxFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) + can.Bus(interface="ixxat", channel=0, rxFifoSize=0) # txFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) + can.Bus(interface="ixxat", channel=0, txFifoSize=0) class HardwareTestCase(unittest.TestCase): @@ -48,22 +45,17 @@ def setUp(self): bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise (unittest.SkipTest()) - - def tearDown(self): - pass + raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(can.CanInitializationError): - bus = can.Bus(interface="ixxat", channel=0xFFFF) + can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): - bus = can.Bus(interface="ixxat", channel=0) - msg = can.Message(arbitration_id=0x3FF, dlc=0) - bus.shutdown() - with self.assertRaises(can.CanOperationError): - bus.send(msg) + with can.Bus(interface="ixxat", channel=0) as bus: + with self.assertRaises(can.CanOperationError): + bus.send(can.Message(arbitration_id=0x3FF, dlc=0)) if __name__ == "__main__": From ba1b9e926a4b13750d54bc9bf6986796ad6e562b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:39:56 +0200 Subject: [PATCH 23/28] rename to CanInterfaceNotImplementedError --- can/__init__.py | 2 +- can/exceptions.py | 19 ++++++++++++------- can/interface.py | 10 +++++----- can/util.py | 6 ++++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index d8bf17a5f..62fdf51fa 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -18,7 +18,7 @@ from .exceptions import ( CanError, - CanBackEndError, + CanInterfaceNotImplementedError, CanInitializationError, CanOperationError, CanTimeoutError, diff --git a/can/exceptions.py b/can/exceptions.py index 55c4b0d82..49ec19544 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -6,13 +6,13 @@ +-- ... +-- CanError (python-can) +-- CanBackendError - +-- CanInitializationError + +-- CanInterfaceNotImplementedError +-- 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 be documented for the function at hand. +:class:`ValueError`. This should always be documented for the function at hand. """ from typing import Optional @@ -49,19 +49,24 @@ def __init__( ) -class CanBackEndError(CanError): - """Indicates an error related to the backend (e.g. driver/OS/library). +class CanInterfaceNotImplementedError(CanError, NotImplementedError): + """Indicates that the interface is not supported on the current platform. Example scenarios: - - The interface does not exist - - The interface is unsupported on the current platform - - The driver is not present or has the wrong version + - 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 :class:`can.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 diff --git a/can/interface.py b/can/interface.py index 5a4d7caa7..7026b37e2 100644 --- a/can/interface.py +++ b/can/interface.py @@ -10,7 +10,7 @@ from .bus import BusABC from .util import load_config from .interfaces import BACKENDS -from .exceptions import CanBackEndError +from .exceptions import CanInterfaceNotImplementedError log = logging.getLogger("can.interface") log_autodetect = log.getChild("detect_available_configs") @@ -38,7 +38,7 @@ def _get_class_for_interface(interface): try: module = importlib.import_module(module_name) except Exception as e: - raise CanBackEndError( + raise CanInterfaceNotImplementedError( "Cannot import module {} for CAN interface '{}': {}".format( module_name, interface, e ) @@ -48,7 +48,7 @@ def _get_class_for_interface(interface): try: bus_class = getattr(module, class_name) except Exception as e: - raise CanBackEndError( + raise CanInterfaceNotImplementedError( "Cannot import class {} from module {} for CAN interface '{}': {}".format( class_name, module_name, interface, e ) @@ -152,9 +152,9 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) - except CanBackEndError: + except CanInterfaceNotImplementedError: log_autodetect.debug( - 'interface "%s" can not be loaded for detection of available configurations', + 'interface "%s" cannot be loaded for detection of available configurations', interface, ) continue diff --git a/can/util.py b/can/util.py index 56b1ca22a..c2b71cf4a 100644 --- a/can/util.py +++ b/can/util.py @@ -17,7 +17,7 @@ import can from .interfaces import VALID_INTERFACES from . import typechecking -from .exceptions import CanBackEndError +from .exceptions import CanInterfaceNotImplementedError log = logging.getLogger("can.util") @@ -191,7 +191,9 @@ def load_config( config[key] = None if config["interface"] not in VALID_INTERFACES: - raise CanBackEndError(f'Invalid CAN Bus Type "{config["interface"]}"') + raise CanInterfaceNotImplementedError( + f'Unknown interface type "{config["interface"]}"' + ) if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) From d193bf24e20754fe4d3b69fc30f30097991d788e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:21:03 +0200 Subject: [PATCH 24/28] cleanup rename to CanInterfaceNotImplementedError --- can/bus.py | 2 +- can/exceptions.py | 2 +- can/interface.py | 7 +++---- can/util.py | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/can/bus.py b/can/bus.py index 945838e54..c13ba2da5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -66,7 +66,7 @@ def __init__( Any backend dependent configurations are passed in this dictionary :raises ValueError: If parameters are out of range - :raises can.CanBackEndError: If the driver cannot be accessed + :raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed :raises can.CanInitializationError: If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] diff --git a/can/exceptions.py b/can/exceptions.py index 49ec19544..146fc4f96 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -5,7 +5,7 @@ Exception (Python standard library) +-- ... +-- CanError (python-can) - +-- CanBackendError + +-- CanInterfaceNotImplementedError +-- CanInterfaceNotImplementedError +-- CanOperationError +-- CanTimeoutError diff --git a/can/interface.py b/can/interface.py index 8e6d86c7a..ce113b18e 100644 --- a/can/interface.py +++ b/can/interface.py @@ -22,9 +22,8 @@ def _get_class_for_interface(interface): :raises: NotImplementedError if the interface is not known - :raises: - CanBackEndError if there was a problem while importing the - interface or the bus class within that + :raises CanInterfaceNotImplementedError: + if there was a problem while importing the interface or the bus class within that """ # Find the correct backend try: @@ -82,7 +81,7 @@ def __new__( Should contain an ``interface`` key with a valid interface name. If not, it is completed using :meth:`can.util.load_config`. - :raises: can.CanBackEndError + :raises: can.CanInterfaceNotImplementedError if the ``interface`` isn't recognized or cannot be loaded :raises: can.CanInitializationError diff --git a/can/util.py b/can/util.py index c2b71cf4a..476130560 100644 --- a/can/util.py +++ b/can/util.py @@ -149,7 +149,7 @@ def load_config( All unused values are passed from ``config`` over to this. :raises: - CanBackEndError if the ``interface`` isn't recognized + CanInterfaceNotImplementedError if the ``interface`` isn't recognized """ # start with an empty dict to apply filtering to all sources From 50e1507c7ddf7d44c759d0613e8c1427f53481c2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:26:47 +0200 Subject: [PATCH 25/28] fix IXXAT test --- test/test_interface_ixxat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 9e0cf550e..55618c769 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -18,7 +18,7 @@ def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() - except ImportError: + except can.CanInterfaceNotImplementedError: raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): @@ -44,7 +44,7 @@ def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() - except ImportError: + except can.CanInterfaceNotImplementedError: raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): From cbe3fb23e43142ad38d385123b57d3236f110f7c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:31:40 +0200 Subject: [PATCH 26/28] defer adjusting test_vector_error_pickle --- test/test_vector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_vector.py b/test/test_vector.py index c00bdb7e7..69edd6d38 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -281,9 +281,10 @@ def test_called_without_testing_argument(self) -> None: """This tests if an exception is thrown when we are not running on Windows.""" if os.name != "nt": with self.assertRaises(OSError): - # do not set the _testing argument, since it supresses the exception + # do not set the _testing argument, since it suppresses the exception can.Bus(channel=0, bustype="vector") + @unittest.skip("Fixing this is deferred until Vector is adjusted after #1025") def test_vector_error_pickle(self) -> None: error_code = 118 error_string = "XL_ERROR" From f68567859b3c40675c0b9dc991808713dca73591 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 27 Apr 2021 13:19:00 +0200 Subject: [PATCH 27/28] update IXXAT to new exceptions --- can/interfaces/ixxat/canlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 9abef1210..13709c71c 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -16,7 +16,7 @@ from typing import Optional from can import BusABC, Message -from can.exceptions import * +from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, @@ -426,7 +426,7 @@ def __init__(self, channel, can_filters=None, **kwargs): Channel bitrate in bit/s """ if _canlib is None: - raise ImportError( + raise CanInterfaceNotImplementedError( "The IXXAT VCI library has not been initialized. Check the logs for more details." ) log.info("CAN Filters: %s", can_filters) From 7c2228d565fb7a5e7e8a80679d7c723726cc2122 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 27 Apr 2021 13:34:16 +0200 Subject: [PATCH 28/28] fix linter issue --- can/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index 146fc4f96..f37b95e4c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -41,7 +41,9 @@ class CanError(Exception): """ def __init__( - self, message: str = "", error_code: Optional[int] = None, *args, **kwargs + self, + message: str = "", + error_code: Optional[int] = None, ) -> None: self.error_code = error_code super().__init__(