From 01b9188b8c76217a19323ca85352d7555139e88b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Mon, 29 Apr 2019 21:01:09 +0200 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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')