-
Notifications
You must be signed in to change notification settings - Fork 679
General exception handling (+ changes to the ixxat interface) #562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
felixdivo
merged 12 commits into
hardbyte:unified-exceptions
from
marcel-kanter:exception-handling
May 19, 2019
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
01b9188
Refactoring: Move exceptions to separate file.
marcel-kanter 8697f14
Added tests for ixxat interface.
marcel-kanter b9bea05
Introduced CanBackEndError for exceptions related to the backend.
marcel-kanter 938e440
Raise an CanOperationError when an exception occurs in the send metho…
marcel-kanter 07326ef
Change intentions to 4 spaces to match remote.
marcel-kanter d5be879
Skip the test if there is an ImportError
marcel-kanter e9c7096
Merged in suggested changes from pull request.
marcel-kanter 5d40159
Introduced CanTimeoutError.
marcel-kanter e29c162
Change version to 4.0.0-dev
hardbyte 543163d
Fix imports
marcel-kanter 3cf73c8
Update __init__.py
hardbyte a929ffe
Merge branch 'exception-handling' of https://github.com/marcel-kanter…
marcel-kanter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| """ | ||
| Exception classes. | ||
| """ | ||
|
|
||
| class CanError(Exception): | ||
| """ Base class for all can related exceptions. | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| class CanBackEndError(CanError): | ||
| """ Indicates an error related to the backend (e.g. driver/OS/library) | ||
| Examples: | ||
| - A call to a library function results in an unexpected return value | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| class CanInitializationError(CanError): | ||
| """ Indicates an error related to the initialization. | ||
| Examples for situations when this exception may occur: | ||
| - Try to open a non-existent device and/or channel | ||
| - Try to use an invalid setting, which is ok by value, but not ok for the interface | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| class CanOperationError(CanError): | ||
| """ Indicates an error while in operation. | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| class CanTimeoutError(CanError): | ||
| """ Indicates a timeout of an operation. | ||
| """ | ||
| pass | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,6 @@ | ||
| # coding: utf-8 | ||
|
|
||
| """ | ||
| Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems | ||
|
|
||
| Copyright (C) 2016 Giuseppe Corbelli <giuseppe.corbelli@weightpack.com> | ||
|
|
||
| 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 | ||
|
|
@@ -20,9 +16,9 @@ | |
| import logging | ||
| import sys | ||
|
|
||
| from can import CanError, BusABC, Message | ||
| from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, | ||
| RestartableCyclicTaskABC) | ||
| from can import BusABC, Message | ||
| from can.exceptions import * | ||
| from can.broadcastmanager import LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC | ||
| from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT | ||
|
|
||
| from . import constants, structures | ||
|
|
@@ -43,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: | ||
|
|
@@ -73,10 +65,7 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): | |
| Formatted string | ||
| """ | ||
| #TODO: make sure we don't generate another exception | ||
| return "{} - arguments were {}".format( | ||
| __vciFormatError(library_instance, function, HRESULT), | ||
| arguments | ||
| ) | ||
| return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) | ||
|
|
||
|
|
||
| def __vciFormatError(library_instance, function, HRESULT): | ||
|
|
@@ -117,6 +106,8 @@ def __check_status(result, function, arguments): | |
| # Real return value is an unsigned long | ||
| result = ctypes.c_ulong(result).value | ||
|
|
||
| #print(hex(result), function) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove |
||
|
|
||
| if result == constants.VCI_E_TIMEOUT: | ||
| raise VCITimeout("Function {} timed out".format(function._name)) | ||
| elif result == constants.VCI_E_RXQUEUE_EMPTY: | ||
|
|
@@ -131,7 +122,7 @@ def __check_status(result, function, arguments): | |
| return result | ||
|
|
||
| try: | ||
| # Map all required symbols and initialize library --------------------------- | ||
| # Map all required symbols and initialize library | ||
| #HRESULT VCIAPI vciInitialize ( void ); | ||
| _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) | ||
|
|
||
|
|
@@ -219,18 +210,18 @@ def __check_status(result, function, arguments): | |
|
|
||
|
|
||
| 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", | ||
| } | ||
| #---------------------------------------------------------------------------- | ||
|
|
||
|
|
@@ -301,17 +292,25 @@ def __init__(self, channel, can_filters=None, **kwargs): | |
| 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: | ||
|
|
@@ -333,23 +332,28 @@ def __init__(self, channel, can_filters=None, **kwargs): | |
| 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)) | ||
|
|
||
| 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) | ||
| _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 | ||
|
|
||
| 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.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 | ||
|
|
@@ -364,19 +368,13 @@ def __init__(self, channel, can_filters=None, **kwargs): | |
| 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) | ||
| _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) | ||
| _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 | ||
|
|
@@ -446,17 +444,15 @@ 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]))) | ||
|
|
||
| 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) | ||
|
|
@@ -482,7 +478,17 @@ 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. | ||
|
|
||
| :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() | ||
| message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA | ||
|
|
@@ -496,23 +502,20 @@ def send(self, msg, timeout=None): | |
| ctypes.memmove(message.abData, adapter, len(msg.data)) | ||
|
|
||
| if timeout: | ||
| _canlib.canChannelSendMessage( | ||
| self._channel_handle, int(timeout * 1000), message) | ||
| _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) | ||
| _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) | ||
| return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) | ||
|
|
||
| def shutdown(self): | ||
| if self._scheduler is not None: | ||
|
|
@@ -533,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): | ||
|
|
@@ -558,12 +560,8 @@ 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) | ||
| _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).""" | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add the return code as a parameter, which is
Noneif not explicitly set? Also applies to CanInitializationError. This way, we could unify it a little bit, and it would be opt-in for interfaces where it would be helpful to have the error codes as a user.