Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@

import logging

__version__ = "3.2.0-a0"
__version__ = "4.0.0.dev0"

log = logging.getLogger('can')

rc = dict()


class CanError(IOError):
"""Indicates an error with the CAN network.

"""
pass

from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError

from .listener import Listener, BufferedReader, RedirectReader
try:
Expand Down
37 changes: 37 additions & 0 deletions can/exceptions.py
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
Copy link
Copy Markdown
Collaborator

@felixdivo felixdivo May 13, 2019

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 None if 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.

"""
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
126 changes: 62 additions & 64 deletions can/interfaces/ixxat/canlib.py
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
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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:
Expand All @@ -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)

Expand Down Expand Up @@ -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",
}
#----------------------------------------------------------------------------

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand All @@ -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)."""
Expand Down
11 changes: 5 additions & 6 deletions can/interfaces/ixxat/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems

Copyright (C) 2016 Giuseppe Corbelli <giuseppe.corbelli@weightpack.com>
Copyright (C) 2019 Marcel Kanter <marcel.kanter@googlemail.com>
"""

from can import CanError
from can import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError

__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError']


class VCITimeout(CanError):
class VCITimeout(CanTimeoutError):
""" Wraps the VCI_E_TIMEOUT error """
pass


class VCIError(CanError):
class VCIError(CanOperationError):
""" Try to display errors that occur within the wrapped C library nicely. """
pass

Expand All @@ -27,5 +26,5 @@ def __init__(self):
super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty")


class VCIDeviceNotFoundError(CanError):
class VCIDeviceNotFoundError(CanInitializationError):
pass
8 changes: 7 additions & 1 deletion doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -74,7 +74,7 @@
"Topic :: System :: Networking",
"Topic :: System :: Hardware :: Hardware Drivers",
"Topic :: Utilities"
),
],

# Code
version=version,
Expand Down
Loading