From 56a922885d9b808ec66bef4f9284bff193a77da4 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Fri, 2 Dec 2016 11:21:46 +0100 Subject: [PATCH 01/12] Added minimal configuration file example and parameters description --- doc/interfaces/ixxat.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 3289aac22..ff99ca282 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -20,6 +20,25 @@ Bus .. autoclass:: can.interfaces.ixxat.canlib.IXXATBus +Configuration file +------------------ +The simplest configuration file would be:: + + [default] + interface = ixxat + channel = 0 + +Python-can will search for the first IXXAT device available and open the first channel. +``interface`` and ``channel`` parameters are interpreted by frontend ``can.interfaces.interface`` +module, while the following parameters are optional and are interpreted by IXXAT implementation. + +* ``bitrate`` (default 500000) Channel bitrate +* ``UniqueHardwareId`` (default first device) Unique hardware ID of the IXXAT device +* ``rxFifoSize`` (default 16) Number of RX mailboxes +* ``txFifoSize`` (default 16) Number of TX mailboxes +* ``extended`` (default False) Allow usage of extended IDs + + Internals --------- From f3857fe589e6e253c41d38dbae9c02d340fa604f Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Mon, 5 Dec 2016 14:43:38 +0100 Subject: [PATCH 02/12] Added support for time.perf_counter in python3. Better error messages when hardware cannot be initialized --- can/interfaces/ixxat/canlib.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 2cfe24ffe..8905111e3 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -22,10 +22,10 @@ log = logging.getLogger('can.ixxat') -if sys.version_info > (3, 3): - _timer_function = time.perf_counter -else: +if (sys.version_info.major == 2): _timer_function = time.clock +elif (sys.version_info.major == 3): + _timer_function = time.perf_counter # main ctypes instance if sys.platform == "win32": @@ -210,14 +210,19 @@ def __init__(self, channel, can_filters=None, **config): self._payload = (ctypes.c_byte * 8)() # Search for supplied device - log.info("Searching for unique HW ID %s", UniqueHardwareId) + 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: - # TODO: better error message - raise VCIDeviceNotFoundError("Unique HW ID {} not found".format(UniqueHardwareId)) + 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 @@ -240,7 +245,7 @@ def __init__(self, channel, can_filters=None, **config): 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: From ecb727c29c4b038358ffbfabaffffad9f080b5ed Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Mon, 5 Dec 2016 14:55:14 +0100 Subject: [PATCH 03/12] Leave the IXXATBus name when importing --- can/interfaces/ixxat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index b3640c91f..622cac1a7 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -4,4 +4,4 @@ Copyright (C) 2016 Giuseppe Corbelli """ -from can.interfaces.ixxat.canlib import IXXATBus as Bus +from can.interfaces.ixxat.canlib import IXXATBus From 553132458f421f05c107e6caea83d03c1d52d634 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Mon, 5 Dec 2016 15:01:18 +0100 Subject: [PATCH 04/12] Use time.perf_counter only on python releases >= 3.3 --- can/interfaces/ixxat/canlib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 8905111e3..58e6db3cf 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -22,10 +22,10 @@ log = logging.getLogger('can.ixxat') -if (sys.version_info.major == 2): - _timer_function = time.clock -elif (sys.version_info.major == 3): +if ((sys.version_info.major == 3) and (sys.version_info.minor >= 3)): _timer_function = time.perf_counter +else: + _timer_function = time.clock # main ctypes instance if sys.platform == "win32": From c69da02f2e2766f8309d15c6adecc5516935d306 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Tue, 20 Dec 2016 15:32:47 +0100 Subject: [PATCH 05/12] Update can.rc from Bus.__new__ kwargs --- can/interfaces/interface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/can/interfaces/interface.py b/can/interfaces/interface.py index e3988b841..425eeb15c 100644 --- a/can/interfaces/interface.py +++ b/can/interfaces/interface.py @@ -30,6 +30,11 @@ def __new__(cls, other, channel=None, *args, **kwargs): if can.rc['interface'] == 'socketcan': can.rc['interface'] = choose_socketcan_implementation() + # Update can.rc from kwargs + for kw in ('interface', 'bitrate'): + if kw in kwargs: + can.rc[kw] = kwargs[kw] + if 'interface' not in can.rc or 'channel' not in can.rc or can.rc['interface'] is None: can.log.debug("Loading default configuration") # Load defaults From 2fb6af5d16541048a0a55cfb9009ea58058f74a9 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Tue, 20 Dec 2016 15:34:47 +0100 Subject: [PATCH 06/12] Handle VCI_E_RXQUEUE_EMPTY with explicit VCIRxQueueEmptyError exception. Added two (private) functions to decode error codes --- can/interfaces/ixxat/canlib.py | 39 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 58e6db3cf..f1e219898 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -6,6 +6,7 @@ import binascii import ctypes +import functools import logging import sys import time @@ -16,9 +17,10 @@ from can.ctypesutil import CLibrary, HANDLE, PHANDLE +from .constants import VCI_MAX_ERRSTRLEN from .exceptions import * -__all__ = ["VCITimeout", "VCIError", "VCIDeviceNotFoundError", "IXXATBus"] +__all__ = ["VCITimeout", "VCIError", "VCIDeviceNotFoundError", "IXXATBus", "vciFormatError"] log = logging.getLogger('can.ixxat') @@ -27,6 +29,9 @@ else: _timer_function = time.clock +# Hack to have vciFormatError as a free function, see below +vciFormatError = None + # main ctypes instance if sys.platform == "win32": try: @@ -40,6 +45,25 @@ log.warning("IXXAT VCI library does not work on %s platform", sys.platform) _canlib = None +def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): + """ Format a VCI error and attach failed function, decoded HRESULT and arguments + @TODO: make sure we don't generate another exception + """ + buf = ctypes.create_string_buffer(VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, VCI_MAX_ERRSTRLEN) + return "function {} failed - {} - arguments were {}".format( + function._name, buf.value.decode('utf-8'), arguments + ) + +def __vciFormatError(library_instance, function, HRESULT): + """ Format a VCI error and attach failed function and decoded HRESULT + @TODO: make sure we don't generate another exception + """ + buf = ctypes.create_string_buffer(VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format(function._name, buf.value.decode('utf-8')) def __check_status(result, function, arguments): if isinstance(result, int): @@ -48,10 +72,12 @@ def __check_status(result, function, arguments): 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_OK): - raise VCIError(function, result, arguments) + raise VCIError(vciFormatError(function, result)) return result @@ -62,6 +88,8 @@ def __check_status(result, function, arguments): #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) @@ -308,11 +336,10 @@ def recv(self, timeout=None): _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) except VCITimeout: return None + except VCIRxQueueEmptyError: + return None except VCIError as e: - if (e.HRESULT == constants.VCI_E_RXQUEUE_EMPTY): - return None - else: - raise e + raise e else: if (self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA): tm = _timer_function() From eb39add0ddcbe76321fc53511b291944c0318899 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Tue, 20 Dec 2016 15:35:49 +0100 Subject: [PATCH 07/12] Module does not depend on ctypes to decode error codes anymore (decoding done in canlib). Added VCIRxQueueEmptyError exception --- can/interfaces/ixxat/exceptions.py | 32 ++++++++---------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index c78534ef3..f394c4269 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -4,40 +4,24 @@ Copyright (C) 2016 Giuseppe Corbelli """ -import ctypes - -from . import constants - from can import CanError -__all__ = ['VCITimeout', 'VCIError', 'VCIDeviceNotFoundError'] +__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] class VCITimeout(CanError): + " Wraps the VCI_E_TIMEOUT error " pass class VCIError(CanError): " Try to display errors that occur within the wrapped C library nicely. " + pass + - _ERROR_BUFFER = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - - def __init__(self, function, HRESULT, arguments): - super(VCIError, self).__init__() - self.HRESULT = HRESULT - self.function = function - self.arguments = arguments - - def __str__(self): - return "function {} failed - {} - arguments were {}".format( - self.function.__name__, - self.__get_error_message(), - self.arguments - ) - - def __get_error_message(self): - ctypes.memset(self._ERROR_BUFFER, 0, constants.VCI_MAX_ERRSTRLEN) - vciFormatError(self.HRESULT, self._ERROR_BUFFER, constants.VCI_MAX_ERRSTRLEN) - return "{}".format(self._ERROR_BUFFER) +class VCIRxQueueEmptyError(VCIError): + " Wraps the VCI_E_RXQUEUE_EMPTY error " + def __init__(self): + super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") class VCIDeviceNotFoundError(CanError): From 73e531b4a06111359d8dcd4610ffc86a3ff2edf1 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Tue, 27 Dec 2016 17:01:59 +0100 Subject: [PATCH 08/12] Use triple quotes instead of single ones in class docs --- can/interfaces/ixxat/exceptions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index f394c4269..49ac868e0 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -9,17 +9,17 @@ __all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] class VCITimeout(CanError): - " Wraps the VCI_E_TIMEOUT error " + """ Wraps the VCI_E_TIMEOUT error """ pass class VCIError(CanError): - " Try to display errors that occur within the wrapped C library nicely. " + """ Try to display errors that occur within the wrapped C library nicely. """ pass class VCIRxQueueEmptyError(VCIError): - " Wraps the VCI_E_RXQUEUE_EMPTY error " + """ Wraps the VCI_E_RXQUEUE_EMPTY error """ def __init__(self): super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") From 898d5ce64e0602585ee0ffa59b2a055f34a69b17 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Tue, 27 Dec 2016 17:02:52 +0100 Subject: [PATCH 09/12] Removed code duplication in __vciFormatError/__vciFormatErrorExtended. Added parameters documentation --- can/interfaces/ixxat/canlib.py | 35 +++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index f1e219898..c10f65b15 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -47,18 +47,33 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): """ Format a VCI error and attach failed function, decoded HRESULT and arguments - @TODO: make sure we don't generate another exception + :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 """ - buf = ctypes.create_string_buffer(VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, VCI_MAX_ERRSTRLEN) - return "function {} failed - {} - arguments were {}".format( - function._name, buf.value.decode('utf-8'), arguments + #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 - @TODO: make sure we don't generate another exception + :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(VCI_MAX_ERRSTRLEN) ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) @@ -318,12 +333,12 @@ def _inWaiting(self): return 1 def flush_tx_buffer(self): - " Flushes the transmit buffer on the IXXAT " + """ Flushes the transmit buffer on the IXXAT """ # TODO: no timeout? _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) def recv(self, timeout=None): - " Read a message from IXXAT device. " + """ Read a message from IXXAT device. """ # TODO: handling CAN error messages? if (timeout is None): @@ -338,8 +353,6 @@ def recv(self, timeout=None): return None except VCIRxQueueEmptyError: return None - except VCIError as e: - raise e else: if (self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA): tm = _timer_function() From 9552a3604908fc726ed19dd5174c3adfb1231ec6 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Thu, 29 Dec 2016 10:44:28 +0100 Subject: [PATCH 10/12] Documented __check_status function --- can/interfaces/ixxat/canlib.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index c10f65b15..b00c2a089 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -60,7 +60,7 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): """ #TODO: make sure we don't generate another exception return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT) + __vciFormatError(library_instance, function, HRESULT), arguments ) @@ -81,6 +81,21 @@ def __vciFormatError(library_instance, function, HRESULT): return "function {} failed ({})".format(function._name, buf.value.decode('utf-8')) 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 From c7913de0ca2b95e855a03d2e05d26a1e0567f689 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Fri, 30 Dec 2016 08:31:19 +0100 Subject: [PATCH 11/12] Ignore utf8 decoding error when formatting vci messages --- can/interfaces/ixxat/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index b00c2a089..d386ccb2a 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -78,7 +78,7 @@ def __vciFormatError(library_instance, function, HRESULT): buf = ctypes.create_string_buffer(VCI_MAX_ERRSTRLEN) ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) library_instance.vciFormatError(HRESULT, buf, VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8')) + 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 From 7d54ef5111dea0347709a76906a271b04fa4dda8 Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Fri, 30 Dec 2016 08:31:40 +0100 Subject: [PATCH 12/12] Added link to Weightpack website --- doc/history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/history.rst b/doc/history.rst index c0af1c243..2bd17784d 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -25,7 +25,7 @@ The pcan interface was contributed by Albert Bloomfield in 2013. The usb2can interface was contributed by Joshua Villyard in 2015 The IXXAT VCI interface was contributed by Giuseppe Corbelli and funded -by Weightpack in 2016 +by `Weightpack `__ in 2016 Support for CAN within Python