diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4402a0a53..1c3f71f13 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,34 @@ +Version 3.1.0 +==== + +Major features +-------------- + +Two new interfaces this release: + +- SYSTEC contributed by @idaniel86 in PR #466 +- CANalyst-II contributed by @smeng9 in PR #476 + + +Other notable changes +--------------------- + +- #477 The kvaser interface now supports bus statistics via a custom bus method. +- #434 neovi now supports receiving own messages +- #490 Adding option to override the neovi library name +- #488 Allow simultaneous access to IXXAT cards +- #447 Improvements to serial interface: + * to allow receiving partial messages + * to fix issue with DLC of remote frames + * addition of unit tests +- #497 Small API changes to `Message` and added unit tests +- #471 Fix CAN FD issue in kvaser interface +- #462 Fix `Notifier` issue with asyncio +- #481 Fix PCAN support on OSX +- #455 Fix to `Message` initializer +- Small bugfixes and improvements + + Version 3.0.0 ==== diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 0576c2b66..c8129fb27 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -22,3 +22,7 @@ Boris Wenzlaff Pierre-Luc Tessier Gagné Felix Divo Kristian Sloth Lauszus +Shaoyu Meng +Alexander Mueller +Jan Goeteyn +"ykzheng" diff --git a/README.rst b/README.rst index cf6b45f83..1277403dc 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,7 @@ Example usage receive_own_messages=True) # send a message - message = can.Message(arbitration_id=123, extended_id=True, + message = can.Message(arbitration_id=123, is_extended_id=True, data=[0x11, 0x22, 0x33]) bus.send(message, timeout=0.2) diff --git a/can/__init__.py b/can/__init__.py index c67cef0bd..58b928ef6 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.0.0" +__version__ = "3.1.0" log = logging.getLogger('can') @@ -21,6 +21,7 @@ class CanError(IOError): """ pass + from .listener import Listener, BufferedReader, RedirectReader try: from .listener import AsyncBufferedReader diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6837f0e67..79d586744 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -85,7 +85,7 @@ def modify_data(self, message): class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): - """Exposes more of the full power of the TX_SETUP opcode. + """A Cyclic send task that supports switches send frequency after a set time. """ def __init__(self, channel, message, count, initial_period, subsequent_period): @@ -93,7 +93,7 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): Transmits a message `count` times at `initial_period` then continues to transmit message at `subsequent_period`. - :param can.interface.Bus channel: + :param channel: See interface specific documentation. :param can.Message message: :param int count: :param float initial_period: diff --git a/can/bus.py b/can/bus.py index eb666a6ab..1a637441c 100644 --- a/can/bus.py +++ b/can/bus.py @@ -167,8 +167,8 @@ def send_periodic(self, msg, period, duration=None, store_task=True): - the (optional) duration expires - the Bus instance goes out of scope - the Bus instance is shutdown - - :meth:`Bus.stop_all_periodic_tasks()` is called - - the task's :meth:`Task.stop()` method is called. + - :meth:`BusABC.stop_all_periodic_tasks()` is called + - the task's :meth:`CyclicTask.stop()` method is called. :param can.Message msg: Message to transmit @@ -368,6 +368,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def state(self): """ Return the current state of the hardware + :return: ACTIVE, PASSIVE or ERROR :rtype: NamedTuple """ @@ -377,6 +378,7 @@ def state(self): def state(self, new_state): """ Set the new state of the hardware + :param new_state: BusState.ACTIVE, BusState.PASSIVE or BusState.ERROR """ raise NotImplementedError("Property is not implemented.") diff --git a/can/interface.py b/can/interface.py index 78a18f891..252ef9c75 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -This module contains the base implementation of `can.Bus` as well -as a list of all avalibale backends and some implemented +This module contains the base implementation of :class:`can.BusABC` as well +as a list of all available backends and some implemented CyclicSendTasks. """ @@ -11,7 +11,6 @@ import sys import importlib import logging -import re import can from .bus import BusABC @@ -145,7 +144,7 @@ def detect_available_configs(interfaces=None): - `None` to search in all known interfaces. :rtype: list[dict] :return: an iterable of dicts, each suitable for usage in - the constructor of :class:`can.interface.Bus`. + the constructor of :class:`can.BusABC`. """ # Figure out where to search diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index e02dd2fb4..ec79e51d6 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -21,7 +21,9 @@ 'virtual': ('can.interfaces.virtual', 'VirtualBus'), 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus') + 'slcan': ('can.interfaces.slcan', 'slcanBus'), + 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), + 'systec': ('can.interfaces.systec', 'UcanBus') } BACKENDS.update({ diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py new file mode 100644 index 000000000..35f240a66 --- /dev/null +++ b/can/interfaces/canalystii.py @@ -0,0 +1,168 @@ +from ctypes import * +import logging +import platform +from can import BusABC, Message + +logger = logging.getLogger(__name__) + + +class VCI_INIT_CONFIG(Structure): + _fields_ = [("AccCode", c_int32), + ("AccMask", c_int32), + ("Reserved", c_int32), + ("Filter", c_ubyte), + ("Timing0", c_ubyte), + ("Timing1", c_ubyte), + ("Mode", c_ubyte)] + + +class VCI_CAN_OBJ(Structure): + _fields_ = [("ID", c_uint), + ("TimeStamp", c_int), + ("TimeFlag", c_byte), + ("SendType", c_byte), + ("RemoteFlag", c_byte), + ("ExternFlag", c_byte), + ("DataLen", c_byte), + ("Data", c_ubyte * 8), + ("Reserved", c_byte * 3)] + + +VCI_USBCAN2 = 4 + +STATUS_OK = 0x01 +STATUS_ERR = 0x00 + +TIMING_DICT = { + 5000: (0xBF, 0xFF), + 10000: (0x31, 0x1C), + 20000: (0x18, 0x1C), + 33330: (0x09, 0x6F), + 40000: (0x87, 0xFF), + 50000: (0x09, 0x1C), + 66660: (0x04, 0x6F), + 80000: (0x83, 0xFF), + 83330: (0x03, 0x6F), + 100000: (0x04, 0x1C), + 125000: (0x03, 0x1C), + 200000: (0x81, 0xFA), + 250000: (0x01, 0x1C), + 400000: (0x80, 0xFA), + 500000: (0x00, 0x1C), + 666000: (0x80, 0xB6), + 800000: (0x00, 0x16), + 1000000: (0x00, 0x14), +} + +try: + if platform.system() == "Windows": + CANalystII = WinDLL("./ControlCAN.dll") + else: + CANalystII = CDLL("./libcontrolcan.so") + logger.info("Loaded CANalystII library") +except OSError as e: + CANalystII = None + logger.info("Cannot load CANalystII library") + + +class CANalystIIBus(BusABC): + def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None): + """ + + :param channel: channel number + :param device: device number + :param baud: baud rate + :param Timing0: customize the timing register if baudrate is not specified + :param Timing1: + :param can_filters: filters for packet + """ + super(CANalystIIBus, self).__init__(channel, can_filters) + + if isinstance(channel, (list, tuple)): + self.channels = channel + elif isinstance(channel, int): + self.channels = [channel] + else: + # Assume comma separated string of channels + self.channels = [int(ch.strip()) for ch in channel.split(',')] + + self.device = device + + self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) + + if baud is not None: + try: + Timing0, Timing1 = TIMING_DICT[baud] + except KeyError: + raise ValueError("Baudrate is not supported") + + if Timing0 is None or Timing1 is None: + raise ValueError("Timing registers are not set") + + self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, Timing0, Timing1, 0) + + if CANalystII.VCI_OpenDevice(VCI_USBCAN2, self.device, 0) == STATUS_ERR: + logger.error("VCI_OpenDevice Error") + + for channel in self.channels: + if CANalystII.VCI_InitCAN(VCI_USBCAN2, self.device, channel, byref(self.init_config)) == STATUS_ERR: + logger.error("VCI_InitCAN Error") + self.shutdown() + return + + if CANalystII.VCI_StartCAN(VCI_USBCAN2, self.device, channel) == STATUS_ERR: + logger.error("VCI_StartCAN Error") + self.shutdown() + return + + def send(self, msg, timeout=None): + """ + + :param msg: message to send + :param timeout: timeout is not used here + :return: + """ + extern_flag = 1 if msg.is_extended_id else 0 + raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 1, msg.is_remote_frame, extern_flag, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + + if msg.channel is not None: + channel = msg.channel + elif len(self.channels) == 1: + channel = self.channels[0] + else: + raise ValueError( + "msg.channel must be set when using multiple channels.") + + CANalystII.VCI_Transmit(VCI_USBCAN2, self.device, channel, byref(raw_message), 1) + + def _recv_internal(self, timeout=None): + """ + + :param timeout: float in seconds + :return: + """ + raw_message = VCI_CAN_OBJ() + + timeout = -1 if timeout is None else int(timeout * 1000) + + if CANalystII.VCI_Receive(VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: + return None, False + else: + return ( + Message( + timestamp=raw_message.TimeStamp if raw_message.TimeFlag else 0.0, + arbitration_id=raw_message.ID, + is_remote_frame=raw_message.RemoteFlag, + channel=0, + dlc=raw_message.DataLen, + data=raw_message.Data, + ), + False, + ) + + def flush_tx_buffer(self): + for channel in self.channels: + CANalystII.VCI_ClearBuffer(VCI_USBCAN2, self.device, channel) + + def shutdown(self): + CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index e138631fb..82bb7d28d 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -75,6 +75,8 @@ def __init__(self, channel, can_filters=None, **config): :type channel: int or str or list(int) or list(str) :param list can_filters: See :meth:`can.BusABC.set_filters` for details. + :param bool receive_own_messages: + If transmitted messages should also be received by this bus. :param bool use_system_timestamp: Use system timestamp for can messages instead of the hardware time stamp @@ -89,6 +91,8 @@ def __init__(self, channel, can_filters=None, **config): :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. + :param override_library_name: + Absolute path or relative path to the library including filename. """ if ics is None: raise ImportError('Please install python-ics') @@ -99,6 +103,9 @@ def __init__(self, channel, can_filters=None, **config): logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(config)) + if 'override_library_name' in config: + ics.override_library_name(config.get('override_library_name')) + if isinstance(channel, (list, tuple)): self.channels = channel elif isinstance(channel, int): @@ -127,6 +134,7 @@ def __init__(self, channel, can_filters=None, **config): self._use_system_timestamp = bool( config.get('use_system_timestamp', False) ) + self._receive_own_messages = config.get('receive_own_messages', True) self.channel_info = '%s %s CH:%s' % ( self.dev.Name, @@ -222,6 +230,9 @@ def _process_msg_queue(self, timeout=0.1): for ics_msg in messages: if ics_msg.NetworkID not in self.channels: continue + is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG) + if not self._receive_own_messages and is_tx: + continue self.rx_buffer.append(ics_msg) if errors: logger.warning("%d error(s) found" % errors) @@ -262,7 +273,7 @@ def _ics_msg_to_message(self, ics_msg): arbitration_id=ics_msg.ArbIDOrHeader, data=data, dlc=ics_msg.NumberBytesData, - extended_id=bool( + is_extended_id=bool( ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME ), is_fd=is_fd, @@ -283,7 +294,7 @@ def _ics_msg_to_message(self, ics_msg): arbitration_id=ics_msg.ArbIDOrHeader, data=ics_msg.Data[:ics_msg.NumberBytesData], dlc=ics_msg.NumberBytesData, - extended_id=bool( + is_extended_id=bool( ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME ), is_fd=is_fd, diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index da8dbffa4..a646bd96e 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -111,7 +111,7 @@ def _recv_internal(self, timeout): break msg = Message(arbitration_id=raw_msg.message_id, - extended_id=bool(raw_msg.is_extended), + is_extended_id=bool(raw_msg.is_extended), timestamp=time.time(), # Better than nothing... is_remote_frame=bool(raw_msg.remote_req), dlc=raw_msg.data_len, @@ -147,6 +147,7 @@ class IscanError(CanError): 10: "Thread already started", 11: "Buffer overrun", 12: "Device not initialized", + 15: "Found the device, but it is being used by another process", 16: "Bus error", 17: "Bus off", 18: "Error passive", diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 51812b147..e48323e01 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -123,6 +123,8 @@ def __check_status(result, function, arguments): 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)) @@ -470,7 +472,7 @@ def _recv_internal(self, timeout): 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, - extended_id=True if self._message.uMsgInfo.Bits.ext 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], diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 2d8305239..34f7f6e7c 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -18,6 +18,7 @@ from can import CanError, BusABC from can import Message from . import constants as canstat +from . import structures log = logging.getLogger('can.kvaser') @@ -248,6 +249,18 @@ def __check_bus_handle_validity(handle, function, arguments): restype=canstat.c_canStatus, errcheck=__check_status) + canRequestBusStatistics = __get_canlib_function("canRequestBusStatistics", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status) + + canGetBusStatistics = __get_canlib_function("canGetBusStatistics", + argtypes=[c_canHandle, + ctypes.POINTER(structures.BusStatistics), + ctypes.c_size_t], + restype=canstat.c_canStatus, + errcheck=__check_status) + def init_kvaser_library(): if __canlib is not None: @@ -396,7 +409,7 @@ def __init__(self, channel, can_filters=None, **config): elif not data_bitrate: # Use same bitrate for arbitration and data phase data_bitrate = bitrate - canSetBusParamsFd(self._read_handle, bitrate, tseg1, tseg2, sjw) + canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) else: if 'tseg1' not in config and bitrate in BITRATE_OBJS: bitrate = BITRATE_OBJS[bitrate] @@ -508,7 +521,7 @@ def _recv_internal(self, timeout=None): rx_msg = Message(arbitration_id=arb_id.value, data=data_array[:dlc.value], dlc=dlc.value, - extended_id=is_extended, + is_extended_id=is_extended, is_error_frame=is_error_frame, is_remote_frame=is_remote_frame, is_fd=is_fd, @@ -572,6 +585,25 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) + def get_stats(self): + """Retrieves the bus statistics. + + Use like so: + + >>> stats = bus.get_stats() + >>> print(stats) + std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 + + :returns: bus statistics. + :rtype: can.interfaces.kvaser.structures.BusStatistics + """ + canRequestBusStatistics(self._write_handle) + stats = structures.BusStatistics() + canGetBusStatistics(self._write_handle, + ctypes.pointer(stats), + ctypes.sizeof(stats)) + return stats + @staticmethod def _detect_available_configs(): num_channels = ctypes.c_int(0) diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py new file mode 100644 index 000000000..21eefe86f --- /dev/null +++ b/can/interfaces/kvaser/structures.py @@ -0,0 +1,73 @@ +# coding: utf-8 + +""" +Contains Python equivalents of the structures in CANLIB's canlib.h, +with some supporting functionality specific to Python. +""" + +import ctypes + + +class BusStatistics(ctypes.Structure): + """ + This structure is used with the method :meth:`KvaserBus.get_stats`. + + .. seealso:: :meth:`KvaserBus.get_stats` + + """ + _fields_ = [ + ("m_stdData", ctypes.c_ulong), + ("m_stdRemote", ctypes.c_ulong), + ("m_extData", ctypes.c_ulong), + ("m_extRemote", ctypes.c_ulong), + ("m_errFrame", ctypes.c_ulong), + ("m_busLoad", ctypes.c_ulong), + ("m_overruns", ctypes.c_ulong) + ] + + def __str__(self): + return ("std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " + "err_frame: {}, bus_load: {:.1f}%, overruns: {}").format( + self.std_data, + self.std_remote, + self.ext_data, + self.ext_remote, + self.err_frame, + self.bus_load / 100.0, + self.overruns, + ) + + @property + def std_data(self): + """Number of received standard (11-bit identifiers) data frames.""" + return self.m_stdData + + @property + def std_remote(self): + """Number of received standard (11-bit identifiers) remote frames.""" + return self.m_stdRemote + + @property + def ext_data(self): + """Number of received extended (29-bit identifiers) data frames.""" + return self.m_extData + + @property + def ext_remote(self): + """Number of received extended (29-bit identifiers) remote frames.""" + return self.m_extRemote + + @property + def err_frame(self): + """Number of error frames.""" + return self.m_errFrame + + @property + def bus_load(self): + """The bus load, expressed as an integer in the interval 0 - 10000 representing 0.00% - 100.00% bus load.""" + return self.m_busLoad + + @property + def overruns(self): + """Number of overruns.""" + return self.m_overruns diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 38cca7504..0e962cd2f 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -245,7 +245,7 @@ def _recv_internal(self, timeout): channel=self.channel, is_remote_frame=is_remote_frame, is_error_frame=is_error_frame, - extended_id=is_extended, + is_extended_id=is_extended, arbitration_id=arb_id, dlc=dlc, data=raw_msg.data[:dlc]) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 322c6bbd4..053197119 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -288,6 +288,16 @@ class TPCANMsg (Structure): + """ + Represents a PCAN message + """ + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("LEN", c_ubyte), # Data Length Code of the message (0..8) + ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) + + +class TPCANMsgMac (Structure): """ Represents a PCAN message """ @@ -298,6 +308,16 @@ class TPCANMsg (Structure): class TPCANTimestamp (Structure): + """ + Represents a timestamp of a received PCAN message + Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow + """ + _fields_ = [ ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 + ("millis_overflow", c_ushort), # Roll-arounds of millis + ("micros", c_ushort) ] # Microseconds: 0..999 + + +class TPCANTimestampMac (Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow @@ -308,6 +328,15 @@ class TPCANTimestamp (Structure): class TPCANMsgFD (Structure): + """ + Represents a PCAN message + """ + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("DLC", c_ubyte), # Data Length Code of the message (0..15) + ("DATA", c_ubyte * 64) ] # Data of the message (DATA[0]..DATA[63]) + +class TPCANMsgFDMac (Structure): """ Represents a PCAN message """ @@ -484,8 +513,12 @@ def Read( A touple with three values """ try: - msg = TPCANMsg() - timestamp = TPCANTimestamp() + if platform.system() == 'Darwin': + msg = TPCANMsgMac() + timestamp = TPCANTimestampMac() + else: + msg = TPCANMsg() + timestamp = TPCANTimestamp() res = self.__m_dllBasic.CAN_Read(Channel,byref(msg),byref(timestamp)) return TPCANStatus(res),msg,timestamp except: @@ -514,7 +547,10 @@ def ReadFD( A touple with three values """ try: - msg = TPCANMsgFD() + if platform.system() == 'Darwin': + msg = TPCANMsgFDMac() + else: + msg = TPCANMsgFD() timestamp = TPCANTimestampFD() res = self.__m_dllBasic.CAN_ReadFD(Channel,byref(msg),byref(timestamp)) return TPCANStatus(res),msg,timestamp diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 0c1881d29..09e9457b4 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -218,7 +218,7 @@ def _recv_internal(self, timeout): rx_msg = Message(timestamp=timestamp, arbitration_id=theMsg.ID, - extended_id=bIsExt, + is_extended_id=bIsExt, is_remote_frame=bIsRTR, dlc=dlc, data=theMsg.DATA[:dlc]) @@ -232,7 +232,10 @@ def send(self, msg, timeout=None): msgType = PCAN_MESSAGE_STANDARD # create a TPCANMsg message structure - CANMsg = TPCANMsg() + if platform.system() == 'Darwin': + CANMsg = TPCANMsgMac() + else: + CANMsg = TPCANMsg() # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index d90107414..afa545734 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -119,7 +119,7 @@ def _recv_internal(self, timeout): Received message and False (because not filtering as taken place). .. warning:: - Flags like extended_id, is_remote_frame and is_error_frame + Flags like is_extended_id, is_remote_frame and is_error_frame will not be set over this function, the flags in the return message are the default values. diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f115c239a..7b276a078 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -47,7 +47,10 @@ class slcanBus(BusABC): _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds + LINE_TERMINATOR = b'\r' + def __init__(self, channel, ttyBaudrate=115200, bitrate=None, + sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, rtscts=False, **kwargs): """ :param str channel: @@ -59,6 +62,8 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, Bitrate in bit/s :param float poll_interval: Poll interval in seconds when reading messages + :param float sleep_after_open: + Time to wait in seconds after opening serial connection :param bool rtscts: turn hardware handshake (RTS/CTS) on and off """ @@ -72,7 +77,9 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, self.serialPortOrig = serial.serial_for_url( channel, baudrate=ttyBaudrate, rtscts=rtscts) - time.sleep(self._SLEEP_AFTER_SERIAL_OPEN) + self._buffer = bytearray() + + time.sleep(sleep_after_open) if bitrate is not None: self.close() @@ -87,9 +94,7 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, bitrate=None, rtscts=False, **kwargs) def write(self, string): - if not string.endswith('\r'): - string += '\r' - self.serialPortOrig.write(string.encode()) + self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() def open(self): @@ -107,67 +112,79 @@ def _recv_internal(self, timeout): extended = False frame = [] - readStr = self.serialPortOrig.read_until(b'\r') + # First read what is already in the receive buffer + while (self.serialPortOrig.in_waiting and + self.LINE_TERMINATOR not in self._buffer): + self._buffer += self.serialPortOrig.read(1) - if not readStr: + # If we still don't have a complete message, do a blocking read + if self.LINE_TERMINATOR not in self._buffer: + self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR) + + if self.LINE_TERMINATOR not in self._buffer: + # Timed out return None, False - else: - readStr = readStr.decode() - if readStr[0] == 'T': - # extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) - extended = True - for i in range(0, dlc): - frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) - elif readStr[0] == 't': - # normal frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) - for i in range(0, dlc): - frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) - elif readStr[0] == 'r': - # remote frame - canId = int(readStr[1:4], 16) - remote = True - elif readStr[0] == 'R': - # remote extended frame - canId = int(readStr[1:9], 16) - extended = True - remote = True - - if canId is not None: - msg = Message(arbitration_id=canId, - extended_id=extended, - timestamp=time.time(), # Better than nothing... - is_remote_frame=remote, - dlc=dlc, - data=frame) - return msg, False - else: - return None, False - def send(self, msg, timeout=0): + readStr = self._buffer.decode() + del self._buffer[:] + if not readStr: + pass + elif readStr[0] == 'T': + # extended frame + canId = int(readStr[1:9], 16) + dlc = int(readStr[9]) + extended = True + for i in range(0, dlc): + frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) + elif readStr[0] == 't': + # normal frame + canId = int(readStr[1:4], 16) + dlc = int(readStr[4]) + for i in range(0, dlc): + frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) + elif readStr[0] == 'r': + # remote frame + canId = int(readStr[1:4], 16) + dlc = int(readStr[4]) + remote = True + elif readStr[0] == 'R': + # remote extended frame + canId = int(readStr[1:9], 16) + dlc = int(readStr[9]) + extended = True + remote = True + + if canId is not None: + msg = Message(arbitration_id=canId, + is_extended_id=extended, + timestamp=time.time(), # Better than nothing... + is_remote_frame=remote, + dlc=dlc, + data=frame) + return msg, False + return None, False + + def send(self, msg, timeout=None): if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout if msg.is_remote_frame: if msg.is_extended_id: - sendStr = "R%08X0" % (msg.arbitration_id) + sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc) else: - sendStr = "r%03X0" % (msg.arbitration_id) + sendStr = "r%03X%d" % (msg.arbitration_id, msg.dlc) else: if msg.is_extended_id: sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc) else: sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) - for i in range(0, msg.dlc): - sendStr += "%02X" % msg.data[i] + sendStr += "".join(["%02X" % b for b in msg.data]) self.write(sendStr) def shutdown(self): self.close() + self.serialPortOrig.close() def fileno(self): if hasattr(self.serialPortOrig, 'fileno'): diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 24a174867..566ddfcb5 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -303,8 +303,7 @@ def start(self): class MultiRateCyclicSendTask(CyclicSendTask): """Exposes more of the full power of the TX_SETUP opcode. - Transmits a message `count` times at `initial_period` then - continues to transmit message at `subsequent_period`. + """ def __init__(self, channel, message, count, initial_period, subsequent_period): @@ -422,7 +421,7 @@ def capture_message(sock, get_channel=False): msg = Message(timestamp=timestamp, channel=channel, arbitration_id=arbitration_id, - extended_id=is_extended_frame_format, + is_extended_id=is_extended_frame_format, is_remote_frame=is_remote_transmission_request, is_error_frame=is_error_frame, is_fd=is_fd, diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py new file mode 100644 index 000000000..ed8eb8eb7 --- /dev/null +++ b/can/interfaces/systec/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +""" +""" + +from can.interfaces.systec.ucanbus import UcanBus diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py new file mode 100644 index 000000000..64122dac9 --- /dev/null +++ b/can/interfaces/systec/constants.py @@ -0,0 +1,653 @@ +# coding: utf-8 + +from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD + +#: Maximum number of modules that are supported. +MAX_MODULES = 64 + +#: Maximum number of applications that can use the USB-CAN-library. +MAX_INSTANCES = 64 + +#: With the method :meth:`UcanServer.init_can` the module is used, which is detected at first. +#: This value only should be used in case only one module is connected to the computer. +ANY_MODULE = 255 + +#: No valid USB-CAN Handle (only used internally). +INVALID_HANDLE = 0xFF + + +class Baudrate(WORD): + """ + Specifies pre-defined baud rate values for GW-001, GW-002 and all systec USB-CANmoduls. + + .. seealso:: + + :meth:`UcanServer.init_can` + + :meth:`UcanServer.set_baudrate` + + :meth:`UcanServer.get_baudrate_message` + + :class:`BaudrateEx` + """ + + #: 1000 kBit/sec + BAUD_1MBit = 0x14 + #: 800 kBit/sec + BAUD_800kBit = 0x16 + #: 500 kBit/sec + BAUD_500kBit = 0x1C + #: 250 kBit/sec + BAUD_250kBit = 0x11C + #: 125 kBit/sec + BAUD_125kBit = 0x31C + #: 100 kBit/sec + BAUD_100kBit = 0x432F + #: 50 kBit/sec + BAUD_50kBit = 0x472F + #: 20 kBit/sec + BAUD_20kBit = 0x532F + #: 10 kBit/sec + BAUD_10kBit = 0x672F + #: Uses pre-defined extended values of baudrate for all systec USB-CANmoduls. + BAUD_USE_BTREX = 0x0 + #: Automatic baud rate detection (not implemented in this version). + BAUD_AUTO = -1 + + +class BaudrateEx(DWORD): + """ + Specifies pre-defined baud rate values for all systec USB-CANmoduls. + + These values cannot be used for GW-001 and GW-002! Use values from enum :class:`Baudrate` instead. + + .. seealso:: + + :meth:`UcanServer.init_can` + + :meth:`UcanServer.set_baudrate` + + :meth:`UcanServer.get_baudrate_ex_message` + + :class:`Baudrate` + """ + + #: G3: 1000 kBit/sec + BAUDEX_1MBit = 0x20354 + #: G3: 800 kBit/sec + BAUDEX_800kBit = 0x30254 + #: G3: 500 kBit/sec + BAUDEX_500kBit = 0x50354 + #: G3: 250 kBit/sec + BAUDEX_250kBit = 0xB0354 + #: G3: 125 kBit/sec + BAUDEX_125kBit = 0x170354 + #: G3: 100 kBit/sec + BAUDEX_100kBit = 0x170466 + #: G3: 50 kBit/sec + BAUDEX_50kBit = 0x2F0466 + #: G3: 20 kBit/sec + BAUDEX_20kBit = 0x770466 + #: G3: 10 kBit/sec (half CPU clock) + BAUDEX_10kBit = 0x80770466 + #: G3: 1000 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_1MBit = 0x20741 + #: G3: 800 kBit/sec Sample Point: 86,67% + BAUDEX_SP2_800kBit = 0x30731 + #: G3: 500 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_500kBit = 0x50741 + #: G3: 250 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_250kBit = 0xB0741 + #: G3: 125 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_125kBit = 0x170741 + #: G3: 100 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_100kBit = 0x1D1741 + #: G3: 50 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_50kBit = 0x3B1741 + #: G3: 20 kBit/sec Sample Point: 85,00% + BAUDEX_SP2_20kBit = 0x771772 + #: G3: 10 kBit/sec Sample Point: 85,00% (half CPU clock) + BAUDEX_SP2_10kBit = 0x80771772 + + #: G4: 1000 kBit/sec Sample Point: 83,33% + BAUDEX_G4_1MBit = 0x406F0000 + #: G4: 800 kBit/sec Sample Point: 80,00% + BAUDEX_G4_800kBit = 0x402A0001 + #: G4: 500 kBit/sec Sample Point: 83,33% + BAUDEX_G4_500kBit = 0x406F0001 + #: G4: 250 kBit/sec Sample Point: 83,33% + BAUDEX_G4_250kBit = 0x406F0003 + #: G4: 125 kBit/sec Sample Point: 83,33% + BAUDEX_G4_125kBit = 0x406F0007 + #: G4: 100 kBit/sec Sample Point: 83,33% + BAUDEX_G4_100kBit = 0x416F0009 + #: G4: 50 kBit/sec Sample Point: 83,33% + BAUDEX_G4_50kBit = 0x416F0013 + #: G4: 20 kBit/sec Sample Point: 84,00% + BAUDEX_G4_20kBit = 0x417F002F + #: G4: 10 kBit/sec Sample Point: 84,00% (half CPU clock) + BAUDEX_G4_10kBit = 0x417F005F + #: Uses pre-defined values of baud rates of :class:`Baudrate`. + BAUDEX_USE_BTR01 = 0x0 + #: Automatic baud rate detection (not implemented in this version). + BAUDEX_AUTO = 0xFFFFFFFF + + +class MsgFrameFormat(BYTE): + """ + Specifies values for the frame format of CAN messages for member :attr:`CanMsg.m_bFF` in structure + :class:`CanMsg`. These values can be combined. + + .. seealso:: :class:`CanMsg` + """ + + #: standard CAN data frame with 11 bit ID (CAN2.0A spec.) + MSG_FF_STD = 0x0 + #: transmit echo + MSG_FF_ECHO = 0x20 + #: CAN remote request frame with + MSG_FF_RTR = 0x40 + #: extended CAN data frame with 29 bit ID (CAN2.0B spec.) + MSG_FF_EXT = 0x80 + + +class ReturnCode(BYTE): + """ + Specifies all return codes of all methods of this class. + """ + + #: no error + SUCCESSFUL = 0x0 + # start of error codes coming from USB-CAN-library + ERR = 0x1 + # start of error codes coming from command interface between host and USB-CANmodul + ERRCMD = 0x40 + # start of warning codes + WARNING = 0x80 + # start of reserved codes which are only used internally + RESERVED = 0xC0 + + #: could not created a resource (memory, handle, ...) + ERR_RESOURCE = 0x1 + #: the maximum number of opened modules is reached + ERR_MAXMODULES = 0x2 + #: the specified module is already in use + ERR_HWINUSE = 0x3 + #: the software versions of the module and library are incompatible + ERR_ILLVERSION = 0x4 + #: the module with the specified device number is not connected (or used by an other application) + ERR_ILLHW = 0x5 + #: wrong USB-CAN-Handle handed over to the function + ERR_ILLHANDLE = 0x6 + #: wrong parameter handed over to the function + ERR_ILLPARAM = 0x7 + #: instruction can not be processed at this time + ERR_BUSY = 0x8 + #: no answer from module + ERR_TIMEOUT = 0x9 + #: a request to the driver failed + ERR_IOFAILED = 0xA + #: a CAN message did not fit into the transmit buffer + ERR_DLL_TXFULL = 0xB + #: maximum number of applications is reached + ERR_MAXINSTANCES = 0xC + #: CAN interface is not yet initialized + ERR_CANNOTINIT = 0xD + #: USB-CANmodul was disconnected + ERR_DISCONECT = 0xE + #: the needed device class does not exist + ERR_NOHWCLASS = 0xF + #: illegal CAN channel + ERR_ILLCHANNEL = 0x10 + #: reserved + ERR_RESERVED1 = 0x11 + #: the API function can not be used with this hardware + ERR_ILLHWTYPE = 0x12 + + #: the received response does not match to the transmitted command + ERRCMD_NOTEQU = 0x40 + #: no access to the CAN controller + ERRCMD_REGTST = 0x41 + #: the module could not interpret the command + ERRCMD_ILLCMD = 0x42 + #: error while reading the EEPROM + ERRCMD_EEPROM = 0x43 + #: reserved + ERRCMD_RESERVED1 = 0x44 + #: reserved + ERRCMD_RESERVED2 = 0x45 + #: reserved + ERRCMD_RESERVED3 = 0x46 + #: illegal baud rate value specified in BTR0/BTR1 for systec USB-CANmoduls + ERRCMD_ILLBDR = 0x47 + #: CAN channel is not initialized + ERRCMD_NOTINIT = 0x48 + #: CAN channel is already initialized + ERRCMD_ALREADYINIT = 0x49 + #: illegal sub-command specified + ERRCMD_ILLSUBCMD = 0x4A + #: illegal index specified (e.g. index for cyclic CAN messages) + ERRCMD_ILLIDX = 0x4B + #: cyclic CAN message(s) can not be defined because transmission of cyclic CAN messages is already running + ERRCMD_RUNNING = 0x4C + + #: no CAN messages received + WARN_NODATA = 0x80 + #: overrun in receive buffer of the kernel driver + WARN_SYS_RXOVERRUN = 0x81 + #: overrun in receive buffer of the USB-CAN-library + WARN_DLL_RXOVERRUN = 0x82 + #: reserved + WARN_RESERVED1 = 0x83 + #: reserved + WARN_RESERVED2 = 0x84 + #: overrun in transmit buffer of the firmware (but this CAN message was successfully stored in buffer of the + #: library) + WARN_FW_TXOVERRUN = 0x85 + #: overrun in receive buffer of the firmware (but this CAN message was successfully read) + WARN_FW_RXOVERRUN = 0x86 + #: reserved + WARN_FW_TXMSGLOST = 0x87 + #: pointer is NULL + WARN_NULL_PTR = 0x90 + #: not all CAN messages could be stored to the transmit buffer in USB-CAN-library (check output of parameter + #: pdwCount_p) + WARN_TXLIMIT = 0x91 + #: reserved + WARN_BUSY = 0x92 + + +class CbEvent(BYTE): + """ + This enum defines events for the callback functions of the library. + + .. seealso:: :meth:`UcanServer.get_status` + """ + + #: The USB-CANmodul has been initialized. + EVENT_INITHW = 0 + #: The CAN interface has been initialized. + EVENT_init_can = 1 + #: A new CAN message has been received. + EVENT_RECEIVE = 2 + #: The error state in the module has changed. + EVENT_STATUS = 3 + #: The CAN interface has been deinitialized. + EVENT_DEINIT_CAN = 4 + #: The USB-CANmodul has been deinitialized. + EVENT_DEINITHW = 5 + #: A new USB-CANmodul has been connected. + EVENT_CONNECT = 6 + #: Any USB-CANmodul has been disconnected. + EVENT_DISCONNECT = 7 + #: A USB-CANmodul has been disconnected during operation. + EVENT_FATALDISCON = 8 + #: Reserved + EVENT_RESERVED1 = 0x80 + + +class CanStatus(WORD): + """ + CAN error status bits. These bit values occurs in combination with the method :meth:`UcanServer.get_status`. + + .. seealso:: + + :meth:`UcanServer.get_status` + + :meth:`UcanServer.get_can_status_message` + """ + + #: No error. + CANERR_OK = 0x0 + #: Transmit buffer of the CAN controller is full. + CANERR_XMTFULL = 0x1 + #: Receive buffer of the CAN controller is full. + CANERR_OVERRUN = 0x2 + #: Bus error: Error Limit 1 exceeded (Warning Limit reached) + CANERR_BUSLIGHT = 0x4 + #: Bus error: Error Limit 2 exceeded (Error Passive) + CANERR_BUSHEAVY = 0x8 + #: Bus error: CAN controller has gone into Bus-Off state. + #: Method :meth:`UcanServer.reset_can` has to be called. + CANERR_BUSOFF = 0x10 + #: No CAN message is within the receive buffer. + CANERR_QRCVEMPTY = 0x20 + #: Receive buffer is full. CAN messages has been lost. + CANERR_QOVERRUN = 0x40 + #: Transmit buffer is full. + CANERR_QXMTFULL = 0x80 + #: Register test of the CAN controller failed. + CANERR_REGTEST = 0x100 + #: Memory test on hardware failed. + CANERR_MEMTEST = 0x200 + #: Transmit CAN message(s) was/were automatically deleted by firmware (transmit timeout). + CANERR_TXMSGLOST = 0x400 + + +class UsbStatus(WORD): + """ + USB error status bits. These bit values occurs in combination with the method :meth:`UcanServer.get_status`. + + .. seealso:: :meth:`UcanServer.get_status` + """ + + #: No error. + USBERR_OK = 0x0 + + +#: Specifies the acceptance mask for receiving all CAN messages. +#: +#: .. seealso:: +#: +#: :const:`ACR_ALL` +#: +#: :meth:`UcanServer.init_can` +#: +#: :meth:`UcanServer.set_acceptance` +AMR_ALL = 0xFFFFFFFF + +#: Specifies the acceptance code for receiving all CAN messages. +#: +#: .. seealso:: +#: +#: :const:`AMR_ALL` +#: +#: :meth:`UcanServer.init_can` +#: +#: :meth:`UcanServer.set_acceptance` +ACR_ALL = 0x0 + + +class OutputControl(BYTE): + """ + Specifies pre-defined values for the Output Control Register of SJA1000 on GW-001 and GW-002. + These values are only important for GW-001 and GW-002. + They does not have an effect on systec USB-CANmoduls. + """ + #: default OCR value for the standard USB-CANmodul GW-001/GW-002 + OCR_DEFAULT = 0x1A + #: OCR value for RS485 interface and galvanic isolation + OCR_RS485_ISOLATED = 0x1E + #: OCR value for RS485 interface but without galvanic isolation + OCR_RS485_NOT_ISOLATED = 0xA + + +#: Specifies the default value for the maximum number of entries in the receive and transmit buffer. +DEFAULT_BUFFER_ENTRIES = 4096 + + +class Channel(BYTE): + """ + Specifies values for the CAN channel to be used on multi-channel USB-CANmoduls. + """ + + #: Specifies the first CAN channel (GW-001/GW-002 and USB-CANmodul1 only can be used with this channel). + CHANNEL_CH0 = 0 + #: Specifies the second CAN channel (this channel cannot be used with GW-001/GW-002 and USB-CANmodul1). + CHANNEL_CH1 = 1 + #: Specifies all CAN channels (can only be used with the method :meth:`UcanServer.shutdown`). + CHANNEL_ALL = 254 + #: Specifies the use of any channel (can only be used with the method :meth:`UcanServer.read_can_msg`). + CHANNEL_ANY = 255 + #: Specifies the first CAN channel (equivalent to :data:`CHANNEL_CH0`). + CHANNEL_CAN1 = CHANNEL_CH0 + #: Specifies the second CAN channel (equivalent to :data:`CHANNEL_CH1`). + CHANNEL_CAN2 = CHANNEL_CH1 + #: Specifies the LIN channel (currently not supported by the software). + CHANNEL_LIN = CHANNEL_CH1 + + +class ResetFlags(DWORD): + """ + Specifies flags for resetting USB-CANmodul with method :meth:`UcanServer.reset_can`. + These flags can be used in combination. + + .. seealso:: :meth:`UcanServer.reset_can` + """ + + #: reset everything + RESET_ALL = 0x0 + #: no CAN status reset (only supported for systec USB-CANmoduls) + RESET_NO_STATUS = 0x1 + #: no CAN controller reset + RESET_NO_CANCTRL = 0x2 + #: no transmit message counter reset + RESET_NO_TXCOUNTER = 0x4 + #: no receive message counter reset + RESET_NO_RXCOUNTER = 0x8 + #: no transmit message buffer reset at channel level + RESET_NO_TXBUFFER_CH = 0x10 + #: no transmit message buffer reset at USB-CAN-library level + RESET_NO_TXBUFFER_DLL = 0x20 + #: no transmit message buffer reset at firmware level + RESET_NO_TXBUFFER_FW = 0x80 + #: no receive message buffer reset at channel level + RESET_NO_RXBUFFER_CH = 0x100 + #: no receive message buffer reset at USB-CAN-library level + RESET_NO_RXBUFFER_DLL = 0x200 + #: no receive message buffer reset at kernel driver level + RESET_NO_RXBUFFER_SYS = 0x400 + #: no receive message buffer reset at firmware level + RESET_NO_RXBUFFER_FW = 0x800 + #: complete firmware reset (module will automatically reconnect at USB port in 500msec) + RESET_FIRMWARE = 0xFFFFFFFF + + #: no reset of all message counters + RESET_NO_COUNTER_ALL = (RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER) + #: no reset of transmit message buffers at communication level (firmware, kernel and library) + RESET_NO_TXBUFFER_COMM = (RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW) + #: no reset of receive message buffers at communication level (firmware, kernel and library) + RESET_NO_RXBUFFER_COMM = (RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW) + #: no reset of all transmit message buffers + RESET_NO_TXBUFFER_ALL = (RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM) + #: no reset of all receive message buffers + RESET_NO_RXBUFFER_ALL = (RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM) + #: no reset of all message buffers at communication level (firmware, kernel and library) + RESET_NO_BUFFER_COMM = (RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM) + #: no reset of all message buffers + RESET_NO_BUFFER_ALL = (RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL) + #: reset of the CAN status only + RESET_ONLY_STATUS = (0xFFFF & ~RESET_NO_STATUS) + #: reset of the CAN controller only + RESET_ONLY_CANCTRL = (0xFFFF & ~RESET_NO_CANCTRL) + #: reset of the transmit buffer in firmware only + RESET_ONLY_TXBUFFER_FW = (0xFFFF & ~RESET_NO_TXBUFFER_FW) + #: reset of the receive buffer in firmware only + RESET_ONLY_RXBUFFER_FW = (0xFFFF & ~RESET_NO_RXBUFFER_FW) + #: reset of the specified channel of the receive buffer only + RESET_ONLY_RXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_RXBUFFER_CH) + #: reset of the specified channel of the transmit buffer only + RESET_ONLY_TXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_TXBUFFER_CH) + #: reset of the receive buffer and receive message counter only + RESET_ONLY_RX_BUFF = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER)) + #: reset of the receive buffer and receive message counter (for GW-002) only + RESET_ONLY_RX_BUFF_GW002 = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | + RESET_NO_TXBUFFER_FW)) + #: reset of the transmit buffer and transmit message counter only + RESET_ONLY_TX_BUFF = (0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER)) + #: reset of all buffers and all message counters only + RESET_ONLY_ALL_BUFF = (RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF) + #: reset of all message counters only + RESET_ONLY_ALL_COUNTER = (0xFFFF & ~RESET_NO_COUNTER_ALL) + + +PRODCODE_PID_TWO_CHA = 0x1 +PRODCODE_PID_TERM = 0x1 +PRODCODE_PID_RBUSER = 0x1 +PRODCODE_PID_RBCAN = 0x1 +PRODCODE_PID_G4 = 0x20 +PRODCODE_PID_RESVD = 0x40 + +PRODCODE_MASK_DID = 0xFFFF0000 +PRODCODE_MASK_PID = 0xFFFF +PRODCODE_MASK_PIDG3 = (PRODCODE_MASK_PID & 0xFFFFFFBF) + + +class ProductCode(WORD): + """ + These values defines product codes for all known USB-CANmodul derivatives received in member + :attr:`HardwareInfoEx.m_dwProductCode` of structure :class:`HardwareInfoEx` + with method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: + + :meth:`UcanServer.get_hardware_info` + + :class:`HardwareInfoEx` + """ + + #: Product code for GW-001 (outdated). + PRODCODE_PID_GW001 = 0x1100 + #: Product code for GW-002 (outdated). + PRODCODE_PID_GW002 = 0x1102 + #: Product code for Multiport CAN-to-USB G3. + PRODCODE_PID_MULTIPORT = 0x1103 + #: Product code for USB-CANmodul1 G3. + PRODCODE_PID_BASIC = 0x1104 + #: Product code for USB-CANmodul2 G3. + PRODCODE_PID_ADVANCED = 0x1105 + #: Product code for USB-CANmodul8 G3. + PRODCODE_PID_USBCAN8 = 0x1107 + #: Product code for USB-CANmodul16 G3. + PRODCODE_PID_USBCAN16 = 0x1109 + #: Reserved. + PRODCODE_PID_RESERVED3 = 0x1110 + #: Product code for USB-CANmodul2 G4. + PRODCODE_PID_ADVANCED_G4 = 0x1121 + #: Product code for USB-CANmodul1 G4. + PRODCODE_PID_BASIC_G4 = 0x1122 + #: Reserved. + PRODCODE_PID_RESERVED1 = 0x1144 + #: Reserved. + PRODCODE_PID_RESERVED2 = 0x1145 + + +#: Definitions for cyclic CAN messages. +MAX_CYCLIC_CAN_MSG = 16 + + +class CyclicFlags(DWORD): + """ + Specifies flags for cyclical CAN messages. + These flags can be used in combinations with method :meth:`UcanServer.enable_cyclic_can_msg`. + + .. seealso:: :meth:`UcanServer.enable_cyclic_can_msg` + """ + + #: Stops the transmission of cyclic CAN messages. + CYCLIC_FLAG_STOPP = 0x0 + #: Global enable of transmission of cyclic CAN messages. + CYCLIC_FLAG_START = 0x80000000 + #: List of cyclic CAN messages will be processed in sequential mode (otherwise in parallel mode). + CYCLIC_FLAG_SEQUMODE = 0x40000000 + #: No echo will be sent back if echo mode is enabled with method :meth:`UcanServer.init_can`. + CYCLIC_FLAG_NOECHO = 0x10000 + #: CAN message with index 0 of the list will not be sent. + CYCLIC_FLAG_LOCK_0 = 0x1 + #: CAN message with index 1 of the list will not be sent. + CYCLIC_FLAG_LOCK_1 = 0x2 + #: CAN message with index 2 of the list will not be sent. + CYCLIC_FLAG_LOCK_2 = 0x4 + #: CAN message with index 3 of the list will not be sent. + CYCLIC_FLAG_LOCK_3 = 0x8 + #: CAN message with index 4 of the list will not be sent. + CYCLIC_FLAG_LOCK_4 = 0x10 + #: CAN message with index 5 of the list will not be sent. + CYCLIC_FLAG_LOCK_5 = 0x20 + #: CAN message with index 6 of the list will not be sent. + CYCLIC_FLAG_LOCK_6 = 0x40 + #: CAN message with index 7 of the list will not be sent. + CYCLIC_FLAG_LOCK_7 = 0x80 + #: CAN message with index 8 of the list will not be sent. + CYCLIC_FLAG_LOCK_8 = 0x100 + #: CAN message with index 9 of the list will not be sent. + CYCLIC_FLAG_LOCK_9 = 0x200 + #: CAN message with index 10 of the list will not be sent. + CYCLIC_FLAG_LOCK_10 = 0x400 + #: CAN message with index 11 of the list will not be sent. + CYCLIC_FLAG_LOCK_11 = 0x800 + #: CAN message with index 12 of the list will not be sent. + CYCLIC_FLAG_LOCK_12 = 0x1000 + #: CAN message with index 13 of the list will not be sent. + CYCLIC_FLAG_LOCK_13 = 0x2000 + #: CAN message with index 14 of the list will not be sent. + CYCLIC_FLAG_LOCK_14 = 0x4000 + #: CAN message with index 15 of the list will not be sent. + CYCLIC_FLAG_LOCK_15 = 0x8000 + + +class PendingFlags(BYTE): + """ + Specifies flags for method :meth:`UcanServer.get_msg_pending`. + These flags can be uses in combinations. + + .. seealso:: :meth:`UcanServer.get_msg_pending` + """ + + #: number of pending CAN messages in receive buffer of USB-CAN-library + PENDING_FLAG_RX_DLL = 0x1 + #: reserved + PENDING_FLAG_RX_SYS = 0x2 + #: number of pending CAN messages in receive buffer of firmware + PENDING_FLAG_RX_FW = 0x4 + #: number of pending CAN messages in transmit buffer of USB-CAN-library + PENDING_FLAG_TX_DLL = 0x10 + #: reserved + PENDING_FLAG_TX_SYS = 0x20 + #: number of pending CAN messages in transmit buffer of firmware + PENDING_FLAG_TX_FW = 0x40 + #: number of pending CAN messages in all receive buffers + PENDING_FLAG_RX_ALL = (PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW) + #: number of pending CAN messages in all transmit buffers + PENDING_FLAG_TX_ALL = (PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW) + #: number of pending CAN messages in all buffers + PENDING_FLAG_ALL = (PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL) + + +class Mode(BYTE): + """ + Specifies values for operation mode of a CAN channel. + These values can be combined by OR operation with the method :meth:`UcanServer.init_can`. + """ + + #: normal operation mode (transmitting and receiving) + MODE_NORMAL = 0 + #: listen only mode (receiving only, no ACK at CAN bus) + MODE_LISTEN_ONLY = 1 + #: CAN messages which was sent will be received back with method :meth:`UcanServer.read_can_msg` + MODE_TX_ECHO = 2 + #: reserved (not implemented in this version) + MODE_RX_ORDER_CH = 4 + #: high resolution time stamps in received CAN messages (only available with STM derivatives) + MODE_HIGH_RES_TIMER = 8 + + +class VersionType(BYTE): + """ + Specifies values for receiving the version information of several driver files. + + .. note:: This structure is only used internally. + """ + + #: version of the USB-CAN-library + VER_TYPE_USER_LIB = 1 + #: equivalent to :attr:`VER_TYPE_USER_LIB` + VER_TYPE_USER_DLL = 1 + #: version of USBCAN.SYS (not supported in this version) + VER_TYPE_SYS_DRV = 2 + #: version of firmware in hardware (not supported, use method :meth:`UcanServer.get_fw_version`) + VER_TYPE_FIRMWARE = 3 + #: version of UCANNET.SYS + VER_TYPE_NET_DRV = 4 + #: version of USBCANLD.SYS + VER_TYPE_SYS_LD = 5 + #: version of USBCANL2.SYS + VER_TYPE_SYS_L2 = 6 + #: version of USBCANL3.SYS + VER_TYPE_SYS_L3 = 7 + #: version of USBCANL4.SYS + VER_TYPE_SYS_L4 = 8 + #: version of USBCANL5.SYS + VER_TYPE_SYS_L5 = 9 + #: version of USBCANCP.CPL + VER_TYPE_CPL = 10 diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py new file mode 100644 index 000000000..d3525cd88 --- /dev/null +++ b/can/interfaces/systec/exceptions.py @@ -0,0 +1,89 @@ +# coding: utf-8 + +from .constants import ReturnCode +from can import CanError + + +class UcanException(CanError): + """ Base class for USB can errors. """ + def __init__(self, result, func, arguments): + self.result = result.value + self.func = func + self.arguments = arguments + self.return_msgs = NotImplemented + + def __str__(self): + return "Function %s returned %d: %s" % \ + (self.func.__name__, self.result, self.return_msgs.get(self.result, "unknown")) + + +class UcanError(UcanException): + """ Exception class for errors from USB-CAN-library. """ + def __init__(self, result, func, arguments): + super(UcanError, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", + ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", + ReturnCode.ERR_HWINUSE: "the specified module is already in use", + ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", + ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " + "(or used by an other application)", + ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", + ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", + ReturnCode.ERR_BUSY: "instruction can not be processed at this time", + ReturnCode.ERR_TIMEOUT: "no answer from module", + ReturnCode.ERR_IOFAILED: "a request to the driver failed", + ReturnCode.ERR_DLL_TXFULL: "a CAN message did not fit into the transmit buffer", + ReturnCode.ERR_MAXINSTANCES: "maximum number of applications is reached", + ReturnCode.ERR_CANNOTINIT: "CAN interface is not yet initialized", + ReturnCode.ERR_DISCONECT: "USB-CANmodul was disconnected", + ReturnCode.ERR_NOHWCLASS: "the needed device class does not exist", + ReturnCode.ERR_ILLCHANNEL: "illegal CAN channel", + ReturnCode.ERR_RESERVED1: "reserved", + ReturnCode.ERR_ILLHWTYPE: "the API function can not be used with this hardware", + } + + +class UcanCmdError(UcanException): + """ Exception class for errors from firmware in USB-CANmodul.""" + def __init__(self, result, func, arguments): + super(UcanCmdError, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", + ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", + ReturnCode.ERRCMD_ILLCMD: "the module could not interpret the command", + ReturnCode.ERRCMD_EEPROM: "error while reading the EEPROM", + ReturnCode.ERRCMD_RESERVED1: "reserved", + ReturnCode.ERRCMD_RESERVED2: "reserved", + ReturnCode.ERRCMD_RESERVED3: "reserved", + ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " + "USB-CANmoduls", + ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", + ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", + ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", + ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", + ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " + "cyclic CAN messages is already running", + } + + +class UcanWarning(UcanException): + """ Exception class for warnings, the function has been executed anyway. """ + def __init__(self, result, func, arguments): + super(UcanWarning, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.WARN_NODATA: "no CAN messages received", + ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", + ReturnCode.WARN_DLL_RXOVERRUN: "overrun in receive buffer of the USB-CAN-library", + ReturnCode.WARN_RESERVED1: "reserved", + ReturnCode.WARN_RESERVED2: "reserved", + ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " + "was successfully stored in buffer of the ibrary)", + ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " + "was successfully read)", + ReturnCode.WARN_FW_TXMSGLOST: "reserved", + ReturnCode.WARN_NULL_PTR: "pointer is NULL", + ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " + "USB-CAN-library", + ReturnCode.WARN_BUSY: "reserved" + } diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py new file mode 100644 index 000000000..a521e044f --- /dev/null +++ b/can/interfaces/systec/structures.py @@ -0,0 +1,337 @@ +# coding: utf-8 + +from ctypes import Structure, POINTER, sizeof +from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD, c_long as BOOL, c_void_p as LPVOID +import os +# Workaround for Unix based platforms to be able to load structures for testing, etc... +if os.name == "nt": + from ctypes import WINFUNCTYPE as FUNCTYPE +else: + from ctypes import CFUNCTYPE as FUNCTYPE + +from .constants import MsgFrameFormat + + +class CanMsg(Structure): + """ + Structure of a CAN message. + + .. seealso:: + + :meth:`UcanServer.read_can_msg` + + :meth:`UcanServer.write_can_msg` + + :meth:`UcanServer.define_cyclic_can_msg` + + :meth:`UcanServer.read_cyclic_can_msg` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwID", DWORD), # CAN Identifier + ("m_bFF", BYTE), # CAN Frame Format (see enum :class:`MsgFrameFormat`) + ("m_bDLC", BYTE), # CAN Data Length Code + ("m_bData", BYTE * 8), # CAN Data (array of 8 bytes) + ("m_dwTime", DWORD,) # Receive time stamp in ms (for transmit messages no meaning) + ] + + def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=[]): + super(CanMsg, self).__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + + def __eq__(self, other): + if not isinstance(other, CanMsg): + return False + + return self.id == other.id and self.frame_format == other.frame_format and self.data == other.data + + @property + def id(self): return self.m_dwID + + @id.setter + def id(self, id): self.m_dwID = id + + @property + def frame_format(self): return self.m_bFF + + @frame_format.setter + def frame_format(self, frame_format): self.m_bFF = frame_format + + @property + def data(self): return self.m_bData[:self.m_bDLC] + + @data.setter + def data(self, data): + self.m_bDLC = len(data) + self.m_bData((BYTE * 8)(*data)) + + @property + def time(self): return self.m_dwTime + + +class Status(Structure): + """ + Structure with the error status of CAN and USB. + Use this structure with the method :meth:`UcanServer.get_status` + + .. seealso:: + + :meth:`UcanServer.get_status` + + :meth:`UcanServer.get_can_status_message` + """ + _pack_ = 1 + _fields_ = [ + ("m_wCanStatus", WORD), # CAN error status (see enum :class:`CanStatus`) + ("m_wUsbStatus", WORD), # USB error status (see enum :class:`UsbStatus`) + ] + + def __eq__(self, other): + if not isinstance(other, Status): + return False + + return self.can_status == other.can_status and self.usb_status == other.usb_status + + @property + def can_status(self): return self.m_wCanStatus + + @property + def usb_status(self): return self.m_wUsbStatus + + +class InitCanParam(Structure): + """ + Structure including initialisation parameters used internally in :meth:`UcanServer.init_can`. + + .. note:: This structure is only used internally. + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure (only used internally) + ("m_bMode", BYTE), # selects the mode of CAN controller (see enum :class:`Mode`) + # Baudrate Registers for GW-001 or GW-002 + ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) + ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) + ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) + ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls + # (see enum :class:`BaudrateEx`) + ("m_wNrOfRxBufferEntries", WORD), # number of receive buffer entries (default is 4096) + ("m_wNrOfTxBufferEntries", WORD), # number of transmit buffer entries (default is 4096) + ] + + def __init__(self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries): + super(InitCanParam, self).__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, + baudrate, rx_buffer_entries, tx_buffer_entries) + + def __eq__(self, other): + if not isinstance(other, InitCanParam): + return False + + return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ + self.baudrate == other.baudrate and self.rx_buffer_entries == other.rx_buffer_entries and \ + self.tx_buffer_entries == other.tx_buffer_entries + + @property + def mode(self): return self.m_bMode + + @mode.setter + def mode(self, mode): self.m_bMode = mode + + @property + def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + + @BTR.setter + def BTR(self, BTR): self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR + + @property + def OCR(self): return self.m_bOCR + + @OCR.setter + def OCR(self, OCR): self.m_bOCR = OCR + + @property + def baudrate(self): return self.m_dwBaudrate + + @baudrate.setter + def baudrate(self, baudrate): self.m_dwBaudrate = baudrate + + @property + def rx_buffer_entries(self): return self.m_wNrOfRxBufferEntries + + @rx_buffer_entries.setter + def rx_buffer_entries(self, rx_buffer_entries): self.m_wNrOfRxBufferEntries = rx_buffer_entries + + @property + def tx_buffer_entries(self): return self.m_wNrOfTxBufferEntries + + @rx_buffer_entries.setter + def tx_buffer_entries(self, tx_buffer_entries): self.m_wNrOfTxBufferEntries = tx_buffer_entries + + +class Handle(BYTE): + pass + + +class HardwareInfoEx(Structure): + """ + Structure including hardware information about the USB-CANmodul. + This structure is used with the method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: :meth:`UcanServer.get_hardware_info` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure (only used internally) + ("m_UcanHandle", Handle), # USB-CAN-Handle assigned by the DLL + ("m_bDeviceNr", BYTE), # device number of the USB-CANmodul + ("m_dwSerialNr", DWORD), # serial number from USB-CANmodul + ("m_dwFwVersionEx", DWORD), # version of firmware + ("m_dwProductCode", DWORD), # product code (see enum :class:`ProductCode`) + # unique ID (available since V5.01) !!! m_dwSize must be >= HWINFO_SIZE_V2 + ("m_dwUniqueId0", DWORD), + ("m_dwUniqueId1", DWORD), + ("m_dwUniqueId2", DWORD), + ("m_dwUniqueId3", DWORD), + ("m_dwFlags", DWORD), # additional flags + ] + + def __init__(self): + super(HardwareInfoEx, self).__init__(sizeof(HardwareInfoEx)) + + def __eq__(self, other): + if not isinstance(other, HardwareInfoEx): + return False + + return self.device_number == other.device_number and self.serial == other.serial and \ + self.fw_version == other.fw_version and self.product_code == other.product_code and \ + self.unique_id == other.unique_id and self.flags == other.flags + + @property + def device_number(self): return self.m_bDeviceNr + + @property + def serial(self): return self.m_dwSerialNr + + @property + def fw_version(self): return self.m_dwFwVersionEx + + @property + def product_code(self): return self.m_dwProductCode + + @property + def unique_id(self): return self.m_dwUniqueId0, self.m_dwUniqueId1, self.m_dwUniqueId2, self.m_dwUniqueId3 + + @property + def flags(self): return self.m_dwFlags + + +# void PUBLIC UcanCallbackFktEx (Handle UcanHandle_p, DWORD dwEvent_p, +# BYTE bChannel_p, void* pArg_p); +CallbackFktEx = FUNCTYPE(None, Handle, DWORD, BYTE, LPVOID) + + +class HardwareInitInfo(Structure): + """ + Structure including information about the enumeration of USB-CANmoduls. + + .. seealso:: :meth:`UcanServer.enumerate_hardware` + + .. note:: This structure is only used internally. + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure + ("m_fDoInitialize", BOOL), # specifies if the found module should be initialized by the DLL + ("m_pUcanHandle", Handle), # pointer to variable receiving the USB-CAN-Handle + ("m_fpCallbackFktEx", CallbackFktEx), # pointer to callback function + ("m_pCallbackArg", LPVOID), # pointer to user defined parameter for callback function + ("m_fTryNext", BOOL), # specifies if a further module should be found + ] + + +class ChannelInfo(Structure): + """ + Structure including CAN channel information. + This structure is used with the method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: :meth:`UcanServer.get_hardware_info` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure + ("m_bMode", BYTE), # operation mode of CAN controller (see enum :class:`Mode`) + ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) + ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) + ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) + ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls + # (see enum :class:`BaudrateEx`) + ("m_fCanIsInit", BOOL), # True if the CAN interface is initialized, otherwise false + ("m_wCanStatus", WORD), # CAN status (same as received by method :meth:`UcanServer.get_status`) + ] + + def __init__(self): + super(ChannelInfo, self).__init__(sizeof(ChannelInfo)) + + def __eq__(self, other): + if not isinstance(other, ChannelInfo): + return False + + return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ + self.AMR == other.AMR and self.ACR == other.ACR and self.baudrate == other.baudrate and \ + self.can_is_init == other.can_is_init and self.can_status == other.can_status + + @property + def mode(self): return self.m_bMode + + @property + def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + + @property + def OCR(self): return self.m_bOCR + + @property + def AMR(self): return self.m_dwAMR + + @property + def ACR(self): return self.m_dwACR + + @property + def baudrate(self): return self.m_dwBaudrate + + @property + def can_is_init(self): return self.m_fCanIsInit + + @property + def can_status(self): return self.m_wCanStatus + + +class MsgCountInfo(Structure): + """ + Structure including the number of sent and received CAN messages. + This structure is used with the method :meth:`UcanServer.get_msg_count_info`. + + .. seealso:: :meth:`UcanServer.get_msg_count_info` + + .. note:: This structure is only used internally. + """ + _fields_ = [ + ("m_wSentMsgCount", WORD), # number of sent CAN messages + ("m_wRecvdMsgCount", WORD), # number of received CAN messages + ] + + @property + def sent_msg_count(self): return self.m_wSentMsgCount + + @property + def recv_msg_count(self): return self.m_wRecvdMsgCount + + +# void (PUBLIC *ConnectControlFktEx) (DWORD dwEvent_p, DWORD dwParam_p, void* pArg_p); +ConnectControlFktEx = FUNCTYPE(None, DWORD, DWORD, LPVOID) + +# typedef void (PUBLIC *EnumCallback) (DWORD dwIndex_p, BOOL fIsUsed_p, +# HardwareInfoEx* pHwInfoEx_p, HardwareInitInfo* pInitInfo_p, void* pArg_p); +EnumCallback = FUNCTYPE(None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID) diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py new file mode 100644 index 000000000..e42c187eb --- /dev/null +++ b/can/interfaces/systec/ucan.py @@ -0,0 +1,1073 @@ +# coding: utf-8 + +import logging +import sys + +from ctypes import byref +from ctypes import c_wchar_p as LPWSTR + +from .constants import * +from .structures import * +from .exceptions import * + +log = logging.getLogger("can.systec") + + +def check_valid_rx_can_msg(result): + """ + Checks if function :meth:`UcanServer.read_can_msg` returns a valid CAN message. + + :param ReturnCode result: Error code of the function. + :return: True if a valid CAN messages was received, otherwise False. + :rtype: bool + """ + return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + + +def check_tx_ok(result): + """ + Checks if function :meth:`UcanServer.write_can_msg` successfully wrote CAN message(s). + + While using :meth:`UcanServer.write_can_msg_ex` the number of sent CAN messages can be less than + the number of CAN messages which should be sent. + + :param ReturnCode result: Error code of the function. + :return: True if CAN message(s) was(were) written successfully, otherwise False. + :rtype: bool + + .. :seealso: :const:`ReturnCode.WARN_TXLIMIT` + """ + return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + + +def check_tx_success(result): + """ + Checks if function :meth:`UcanServer.write_can_msg_ex` successfully wrote all CAN message(s). + + :param ReturnCode result: Error code of the function. + :return: True if CAN message(s) was(were) written successfully, otherwise False. + :rtype: bool + """ + return result.value == ReturnCode.SUCCESSFUL + + +def check_tx_not_all(result): + """ + Checks if function :meth:`UcanServer.write_can_msg_ex` did not sent all CAN messages. + + :param ReturnCode result: Error code of the function. + :return: True if not all CAN messages were written, otherwise False. + :rtype: bool + """ + return result.value == ReturnCode.WARN_TXLIMIT + + +def check_warning(result): + """ + Checks if any function returns a warning. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned warning, otherwise False. + :rtype: bool + """ + return result.value >= ReturnCode.WARNING + + +def check_error(result): + """ + Checks if any function returns an error from USB-CAN-library. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned error, otherwise False. + :rtype: bool + """ + return (result.value != ReturnCode.SUCCESSFUL) and (result.value < ReturnCode.WARNING) + + +def check_error_cmd(result): + """ + Checks if any function returns an error from firmware in USB-CANmodul. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned error from firmware, otherwise False. + :rtype: bool + """ + return (result.value >= ReturnCode.ERRCMD) and (result.value < ReturnCode.WARNING) + + +def check_result(result, func, arguments): + if check_warning(result) and (result.value != ReturnCode.WARN_NODATA): + log.warning(UcanWarning(result, func, arguments)) + elif check_error(result): + if check_error_cmd(result): + raise UcanCmdError(result, func, arguments) + else: + raise UcanError(result, func, arguments) + return result + + +if os.name != "nt": + log.warning("SYSTEC ucan library does not work on %s platform.", sys.platform) +else: + from ctypes import WinDLL + + try: + # Select the proper dll architecture + lib = WinDLL('usbcan64.dll' if sys.maxsize > 2 ** 32 else 'usbcan32.dll') + + # BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p); + UcanSetDebugMode = lib.UcanSetDebugMode + UcanSetDebugMode.restype = BOOL + UcanSetDebugMode.argtypes = [DWORD, LPWSTR, DWORD] + + # DWORD PUBLIC UcanGetVersionEx (VersionType VerType_p); + UcanGetVersionEx = lib.UcanGetVersionEx + UcanGetVersionEx.restype = DWORD + UcanGetVersionEx.argtypes = [VersionType] + + # DWORD PUBLIC UcanGetFwVersion (Handle UcanHandle_p); + UcanGetFwVersion = lib.UcanGetFwVersion + UcanGetFwVersion.restype = DWORD + UcanGetFwVersion.argtypes = [Handle] + + # BYTE PUBLIC UcanInitHwConnectControlEx (ConnectControlFktEx fpConnectControlFktEx_p, void* pCallbackArg_p); + UcanInitHwConnectControlEx = lib.UcanInitHwConnectControlEx + UcanInitHwConnectControlEx.restype = ReturnCode + UcanInitHwConnectControlEx.argtypes = [ConnectControlFktEx, LPVOID] + UcanInitHwConnectControlEx.errcheck = check_result + + # BYTE PUBLIC UcanDeinitHwConnectControl (void) + UcanDeinitHwConnectControl = lib.UcanDeinitHwConnectControl + UcanDeinitHwConnectControl.restype = ReturnCode + UcanDeinitHwConnectControl.argtypes = [] + UcanDeinitHwConnectControl.errcheck = check_result + + # DWORD PUBLIC UcanEnumerateHardware (EnumCallback fpCallback_p, void* pCallbackArg_p, + # BOOL fEnumUsedDevs_p, + # BYTE bDeviceNrLow_p, BYTE bDeviceNrHigh_p, + # DWORD dwSerialNrLow_p, DWORD dwSerialNrHigh_p, + # DWORD dwProductCodeLow_p, DWORD dwProductCodeHigh_p); + UcanEnumerateHardware = lib.UcanEnumerateHardware + UcanEnumerateHardware.restype = DWORD + UcanEnumerateHardware.argtypes = [EnumCallback, LPVOID, BOOL, BYTE, BYTE, DWORD, DWORD, DWORD, DWORD] + + # BYTE PUBLIC UcanInitHardwareEx (Handle* pUcanHandle_p, BYTE bDeviceNr_p, + # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); + UcanInitHardwareEx = lib.UcanInitHardwareEx + UcanInitHardwareEx.restype = ReturnCode + UcanInitHardwareEx.argtypes = [POINTER(Handle), BYTE, CallbackFktEx, LPVOID] + UcanInitHardwareEx.errcheck = check_result + + # BYTE PUBLIC UcanInitHardwareEx2 (Handle* pUcanHandle_p, DWORD dwSerialNr_p, + # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); + UcanInitHardwareEx2 = lib.UcanInitHardwareEx2 + UcanInitHardwareEx2.restype = ReturnCode + UcanInitHardwareEx2.argtypes = [POINTER(Handle), DWORD, CallbackFktEx, LPVOID] + UcanInitHardwareEx2.errcheck = check_result + + # BYTE PUBLIC UcanGetModuleTime (Handle UcanHandle_p, DWORD* pdwTime_p); + UcanGetModuleTime = lib.UcanGetModuleTime + UcanGetModuleTime.restype = ReturnCode + UcanGetModuleTime.argtypes = [Handle, POINTER(DWORD)] + UcanGetModuleTime.errcheck = check_result + + # BYTE PUBLIC UcanGetHardwareInfoEx2 (Handle UcanHandle_p, + # HardwareInfoEx* pHwInfo_p, + # ChannelInfo* pCanInfoCh0_p, ChannelInfo* pCanInfoCh1_p); + UcanGetHardwareInfoEx2 = lib.UcanGetHardwareInfoEx2 + UcanGetHardwareInfoEx2.restype = ReturnCode + UcanGetHardwareInfoEx2.argtypes = [Handle, POINTER(HardwareInfoEx), POINTER(ChannelInfo), + POINTER(ChannelInfo)] + UcanGetHardwareInfoEx2.errcheck = check_result + + # BYTE PUBLIC UcanInitCanEx2 (Handle UcanHandle_p, BYTE bChannel_p, tUcaninit_canParam* pinit_canParam_p); + UcanInitCanEx2 = lib.UcanInitCanEx2 + UcanInitCanEx2.restype = ReturnCode + UcanInitCanEx2.argtypes = [Handle, BYTE, POINTER(InitCanParam)] + UcanInitCanEx2.errcheck = check_result + + # BYTE PUBLIC UcanSetBaudrateEx (Handle UcanHandle_p, + # BYTE bChannel_p, BYTE bBTR0_p, BYTE bBTR1_p, DWORD dwBaudrate_p); + UcanSetBaudrateEx = lib.UcanSetBaudrateEx + UcanSetBaudrateEx.restype = ReturnCode + UcanSetBaudrateEx.argtypes = [Handle, BYTE, BYTE, BYTE, DWORD] + UcanSetBaudrateEx.errcheck = check_result + + # BYTE PUBLIC UcanSetAcceptanceEx (Handle UcanHandle_p, BYTE bChannel_p, + # DWORD dwAMR_p, DWORD dwACR_p); + UcanSetAcceptanceEx = lib.UcanSetAcceptanceEx + UcanSetAcceptanceEx.restype = ReturnCode + UcanSetAcceptanceEx.argtypes = [Handle, BYTE, DWORD, DWORD] + UcanSetAcceptanceEx.errcheck = check_result + + # BYTE PUBLIC UcanResetCanEx (Handle UcanHandle_p, BYTE bChannel_p, DWORD dwResetFlags_p); + UcanResetCanEx = lib.UcanResetCanEx + UcanResetCanEx.restype = ReturnCode + UcanResetCanEx.argtypes = [Handle, BYTE, DWORD] + UcanResetCanEx.errcheck = check_result + + # BYTE PUBLIC UcanReadCanMsgEx (Handle UcanHandle_p, BYTE* pbChannel_p, + # CanMsg* pCanMsg_p, DWORD* pdwCount_p); + UcanReadCanMsgEx = lib.UcanReadCanMsgEx + UcanReadCanMsgEx.restype = ReturnCode + UcanReadCanMsgEx.argtypes = [Handle, POINTER(BYTE), POINTER(CanMsg), POINTER(DWORD)] + UcanReadCanMsgEx.errcheck = check_result + + # BYTE PUBLIC UcanWriteCanMsgEx (Handle UcanHandle_p, BYTE bChannel_p, + # CanMsg* pCanMsg_p, DWORD* pdwCount_p); + UcanWriteCanMsgEx = lib.UcanWriteCanMsgEx + UcanWriteCanMsgEx.restype = ReturnCode + UcanWriteCanMsgEx.argtypes = [Handle, BYTE, POINTER(CanMsg), POINTER(DWORD)] + UcanWriteCanMsgEx.errcheck = check_result + + # BYTE PUBLIC UcanGetStatusEx (Handle UcanHandle_p, BYTE bChannel_p, Status* pStatus_p); + UcanGetStatusEx = lib.UcanGetStatusEx + UcanGetStatusEx.restype = ReturnCode + UcanGetStatusEx.argtypes = [Handle, BYTE, POINTER(Status)] + UcanGetStatusEx.errcheck = check_result + + # BYTE PUBLIC UcanGetMsgCountInfoEx (Handle UcanHandle_p, BYTE bChannel_p, + # MsgCountInfo* pMsgCountInfo_p); + UcanGetMsgCountInfoEx = lib.UcanGetMsgCountInfoEx + UcanGetMsgCountInfoEx.restype = ReturnCode + UcanGetMsgCountInfoEx.argtypes = [Handle, BYTE, POINTER(MsgCountInfo)] + UcanGetMsgCountInfoEx.errcheck = check_result + + # BYTE PUBLIC UcanGetMsgPending (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwFlags_p, DWORD* pdwPendingCount_p); + UcanGetMsgPending = lib.UcanGetMsgPending + UcanGetMsgPending.restype = ReturnCode + UcanGetMsgPending.argtypes = [Handle, BYTE, DWORD, POINTER(DWORD)] + UcanGetMsgPending.errcheck = check_result + + # BYTE PUBLIC UcanGetCanErrorCounter (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD* pdwTxErrorCounter_p, DWORD* pdwRxErrorCounter_p); + UcanGetCanErrorCounter = lib.UcanGetCanErrorCounter + UcanGetCanErrorCounter.restype = ReturnCode + UcanGetCanErrorCounter.argtypes = [Handle, BYTE, POINTER(DWORD), POINTER(DWORD)] + UcanGetCanErrorCounter.errcheck = check_result + + # BYTE PUBLIC UcanSetTxTimeout (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwTxTimeout_p); + UcanSetTxTimeout = lib.UcanSetTxTimeout + UcanSetTxTimeout.restype = ReturnCode + UcanSetTxTimeout.argtypes = [Handle, BYTE, DWORD] + UcanSetTxTimeout.errcheck = check_result + + # BYTE PUBLIC UcanDeinitCanEx (Handle UcanHandle_p, BYTE bChannel_p); + UcanDeinitCanEx = lib.UcanDeinitCanEx + UcanDeinitCanEx.restype = ReturnCode + UcanDeinitCanEx.argtypes = [Handle, BYTE] + UcanDeinitCanEx.errcheck = check_result + + # BYTE PUBLIC UcanDeinitHardware (Handle UcanHandle_p); + UcanDeinitHardware = lib.UcanDeinitHardware + UcanDeinitHardware.restype = ReturnCode + UcanDeinitHardware.argtypes = [Handle] + UcanDeinitHardware.errcheck = check_result + + # BYTE PUBLIC UcanDefineCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, CanMsg* pCanMsgList_p, DWORD dwCount_p); + UcanDefineCyclicCanMsg = lib.UcanDefineCyclicCanMsg + UcanDefineCyclicCanMsg.restype = ReturnCode + UcanDefineCyclicCanMsg.argtypes = [Handle, BYTE, POINTER(CanMsg), DWORD] + UcanDefineCyclicCanMsg.errcheck = check_result + + # BYTE PUBLIC UcanReadCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, CanMsg* pCanMsgList_p, DWORD* pdwCount_p); + UcanReadCyclicCanMsg = lib.UcanReadCyclicCanMsg + UcanReadCyclicCanMsg.restype = ReturnCode + UcanReadCyclicCanMsg.argtypes = [Handle, BYTE, POINTER(CanMsg), POINTER(DWORD)] + UcanReadCyclicCanMsg.errcheck = check_result + + # BYTE PUBLIC UcanEnableCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwFlags_p); + UcanEnableCyclicCanMsg = lib.UcanEnableCyclicCanMsg + UcanEnableCyclicCanMsg.restype = ReturnCode + UcanEnableCyclicCanMsg.argtypes = [Handle, BYTE, DWORD] + UcanEnableCyclicCanMsg.errcheck = check_result + + except Exception as ex: + log.warning("Cannot load SYSTEC ucan library: %s.", ex) + + +class UcanServer(object): + """ + UcanServer is a Python wrapper class for using the usbcan32.dll / usbcan64.dll. + """ + _modules_found = [] + _connect_control_ref = None + + def __init__(self): + self._handle = Handle(INVALID_HANDLE) + self._is_initialized = False + self._hw_is_initialized = False + self._ch_is_initialized = { + Channel.CHANNEL_CH0: False, + Channel.CHANNEL_CH1: False + } + self._callback_ref = CallbackFktEx(self._callback) + if self._connect_control_ref is None: + self._connect_control_ref = ConnectControlFktEx(self._connect_control) + UcanInitHwConnectControlEx(self._connect_control_ref, None) + + @property + def is_initialized(self): + """ + Returns whether hardware interface is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._is_initialized + + @property + def is_can0_initialized(self): + """ + Returns whether CAN interface for channel 0 is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._ch_is_initialized[Channel.CHANNEL_CH0] + + @property + def is_can1_initialized(self): + """ + Returns whether CAN interface for channel 1 is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._ch_is_initialized[Channel.CHANNEL_CH1] + + @classmethod + def _enum_callback(cls, index, is_used, hw_info_ex, init_info, arg): + cls._modules_found.append((index, bool(is_used), hw_info_ex.contents, init_info.contents)) + + @classmethod + def enumerate_hardware(cls, device_number_low=0, device_number_high=-1, serial_low=0, serial_high=-1, + product_code_low=0, product_code_high=-1, enum_used_devices=False): + cls._modules_found = [] + UcanEnumerateHardware(cls._enum_callback_ref, None, enum_used_devices, + device_number_low, device_number_high, + serial_low, serial_high, + product_code_low, product_code_high) + return cls._modules_found + + def init_hardware(self, serial=None, device_number=ANY_MODULE): + """ + Initializes the device with the corresponding serial or device number. + + :param int or None serial: Serial number of the USB-CANmodul. + :param int device_number: Device number (0 – 254, or :const:`ANY_MODULE` for the first device). + """ + if not self._hw_is_initialized: + # initialize hardware either by device number or serial + if serial is None: + UcanInitHardwareEx(byref(self._handle), device_number, self._callback_ref, None) + else: + UcanInitHardwareEx2(byref(self._handle), serial, self._callback_ref, None) + self._hw_is_initialized = True + + def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrate=BaudrateEx.BAUDEX_USE_BTR01, + AMR=AMR_ALL, ACR=ACR_ALL, mode=Mode.MODE_NORMAL, OCR=OutputControl.OCR_DEFAULT, + rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, tx_buffer_entries=DEFAULT_BUFFER_ENTRIES): + """ + Initializes a specific CAN channel of a device. + + :param int channel: CAN channel to be initialized (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int BTR: + Baud rate register BTR0 as high byte, baud rate register BTR1 as low byte (see enum :class:`Baudrate`). + :param int baudrate: Baud rate register for all systec USB-CANmoduls (see enum :class:`BaudrateEx`). + :param int AMR: Acceptance filter mask (see method :meth:`set_acceptance`). + :param int ACR: Acceptance filter code (see method :meth:`set_acceptance`). + :param int mode: Transmission mode of CAN channel (see enum :class:`Mode`). + :param int OCR: Output Control Register (see enum :class:`OutputControl`). + :param int rx_buffer_entries: The number of maximum entries in the receive buffer. + :param int tx_buffer_entries: The number of maximum entries in the transmit buffer. + """ + if not self._ch_is_initialized.get(channel, False): + init_param = InitCanParam(mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries) + UcanInitCanEx2(self._handle, channel, init_param) + self._ch_is_initialized[channel] = True + + def read_can_msg(self, channel, count): + """ + Reads one or more CAN-messages from the buffer of the specified CAN channel. + + :param int channel: + CAN channel to read from (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1`, + :data:`Channel.CHANNEL_ANY`). + :param int count: The number of CAN messages to be received. + :return: Tuple with list of CAN message/s received and the CAN channel where the read CAN messages came from. + :rtype: tuple(list(CanMsg), int) + """ + c_channel = BYTE(channel) + c_can_msg = (CanMsg * count)() + c_count = DWORD(count) + UcanReadCanMsgEx(self._handle, byref(c_channel), c_can_msg, byref(c_count)) + return c_can_msg[:c_count.value], c_channel.value + + def write_can_msg(self, channel, can_msg): + """ + Transmits one ore more CAN messages through the specified CAN channel of the device. + + :param int channel: + CAN channel, which is to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param list(CanMsg) can_msg: List of CAN message structure (see structure :class:`CanMsg`). + :return: The number of successfully transmitted CAN messages. + :rtype: int + """ + c_can_msg = (CanMsg * len(can_msg))(*can_msg) + c_count = DWORD(len(can_msg)) + UcanWriteCanMsgEx(self._handle, channel, c_can_msg, c_count) + return c_count + + def set_baudrate(self, channel, BTR, baudarate): + """ + This function is used to configure the baud rate of specific CAN channel of a device. + + :param int channel: + CAN channel, which is to be configured (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int BTR: + Baud rate register BTR0 as high byte, baud rate register BTR1 as low byte (see enum :class:`Baudrate`). + :param int baudarate: Baud rate register for all systec USB-CANmoduls (see enum :class:`BaudrateEx`>). + """ + UcanSetBaudrateEx(self._handle, channel, BTR >> 8, BTR, baudarate) + + def set_acceptance(self, channel=Channel.CHANNEL_CH0, AMR=AMR_ALL, ACR=ACR_ALL): + """ + This function is used to change the acceptance filter values for a specific CAN channel on a device. + + :param int channel: + CAN channel, which is to be configured (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int AMR: Acceptance filter mask (AMR). + :param int ACR: Acceptance filter code (ACR). + """ + UcanSetAcceptanceEx(self._handle, channel, AMR, ACR) + + def get_status(self, channel=Channel.CHANNEL_CH0): + """ + Returns the error status of a specific CAN channel. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with CAN and USB status (see structure :class:`Status`). + :rtype: tuple(int, int) + """ + status = Status() + UcanGetStatusEx(self._handle, channel, byref(status)) + return status.can_status, status.usb_status + + def get_msg_count_info(self, channel=Channel.CHANNEL_CH0): + """ + Reads the message counters of the specified CAN channel. + + :param int channel: + CAN channel, which is to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with number of CAN messages sent and received. + :rtype: tuple(int, int) + """ + msg_count_info = MsgCountInfo() + UcanGetMsgCountInfoEx(self._handle, channel, byref(msg_count_info)) + return msg_count_info.sent_msg_count, msg_count_info.recv_msg_count + + def reset_can(self, channel=Channel.CHANNEL_CH0, flags=ResetFlags.RESET_ALL): + """ + Resets a CAN channel of a device (hardware reset, empty buffer, and so on). + + :param int channel: CAN channel, to be reset (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags defines what should be reset (see enum :class:`ResetFlags`). + """ + UcanResetCanEx(self._handle, channel, flags) + + def get_hardware_info(self): + """ + Returns the extended hardware information of a device. With multi-channel USB-CANmoduls the information for + both CAN channels are returned separately. + + :return: + Tuple with extended hardware information structure (see structure :class:`HardwareInfoEx`) and + structures with information of CAN channel 0 and 1 (see structure :class:`ChannelInfo`). + :rtype: tuple(HardwareInfoEx, ChannelInfo, ChannelInfo) + """ + hw_info_ex = HardwareInfoEx() + can_info_ch0, can_info_ch1 = ChannelInfo(), ChannelInfo() + UcanGetHardwareInfoEx2(self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1)) + return hw_info_ex, can_info_ch0, can_info_ch1 + + def get_fw_version(self): + """ + Returns the firmware version number of the device. + + :return: Firmware version number. + :rtype: int + """ + return UcanGetFwVersion(self._handle) + + def define_cyclic_can_msg(self, channel, can_msg=None): + """ + Defines a list of CAN messages for automatic transmission. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param list(CanMsg) can_msg: + List of CAN messages (up to 16, see structure :class:`CanMsg`), or None to delete an older list. + """ + if can_msg is not None: + c_can_msg = (CanMsg * len(can_msg))(*can_msg) + c_count = DWORD(len(can_msg)) + else: + c_can_msg = CanMsg() + c_count = 0 + UcanDefineCyclicCanMsg(self._handle, channel, c_can_msg, c_count) + + def read_cyclic_can_msg(self, channel, count): + """ + Reads back the list of CAN messages for automatically sending. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int count: The number of cyclic CAN messages to be received. + :return: List of received CAN messages (up to 16, see structure :class:`CanMsg`). + :rtype: list(CanMsg) + """ + c_channel = BYTE(channel) + c_can_msg = (CanMsg * count)() + c_count = DWORD(count) + UcanReadCyclicCanMsg(self._handle, byref(c_channel), c_can_msg, c_count) + return c_can_msg[:c_count.value] + + def enable_cyclic_can_msg(self, channel, flags): + """ + Enables or disables the automatically sending. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags for enabling or disabling (see enum :class:`CyclicFlags`). + """ + UcanEnableCyclicCanMsg(self._handle, channel, flags) + + def get_msg_pending(self, channel, flags): + """ + Returns the number of pending CAN messages. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags specifies which buffers should be checked (see enum :class:`PendingFlags`). + :return: The number of pending messages. + :rtype: int + """ + count = DWORD(0) + UcanGetMsgPending(self._handle, channel, flags, byref(count)) + return count.value + + def get_can_error_counter(self, channel): + """ + Reads the current value of the error counters within the CAN controller. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with the TX and RX error counter. + :rtype: tuple(int, int) + + .. note:: Only available for systec USB-CANmoduls (NOT for GW-001 and GW-002 !!!). + """ + tx_error_counter = DWORD(0) + rx_error_counter = DWORD(0) + UcanGetCanErrorCounter(self._handle, channel, byref(tx_error_counter), byref(rx_error_counter)) + return tx_error_counter, rx_error_counter + + def set_tx_timeout(self, channel, timeout): + """ + Sets the transmission timeout. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param float timeout: Transmit timeout in seconds (value 0 disables this feature). + """ + UcanSetTxTimeout(self._handle, channel, int(timeout * 1000)) + + def shutdown(self, channel=Channel.CHANNEL_ALL, shutdown_hardware=True): + """ + Shuts down all CAN interfaces and/or the hardware interface. + + :param int channel: + CAN channel, to be used (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or + :data:`Channel.CHANNEL_ALL`) + :param bool shutdown_hardware: If true then the hardware interface will be closed too. + """ + # shutdown each channel if it's initialized + for _channel, is_initialized in self._ch_is_initialized.items(): + if is_initialized and (_channel == channel or channel == Channel.CHANNEL_ALL or shutdown_hardware): + UcanDeinitCanEx(self._handle, _channel) + self._ch_is_initialized[_channel] = False + + # shutdown hardware + if self._hw_is_initialized and shutdown_hardware: + UcanDeinitHardware(self._handle) + self._hw_is_initialized = False + self._handle = Handle(INVALID_HANDLE) + + @staticmethod + def get_user_dll_version(): + """ + Returns the version number of the USBCAN-library. + + :return: Software version number. + :rtype: int + """ + return UcanGetVersionEx(VersionType.VER_TYPE_USER_DLL) + + @staticmethod + def set_debug_mode(level, filename, flags=0): + """ + This function enables the creation of a debug log file out of the USBCAN-library. If this + feature has already been activated via the USB-CANmodul Control, the content of the + “old” log file will be copied to the new file. Further debug information will be appended to + the new file. + + :param int level: Debug level (bit format). + :param str filename: File path to debug log file. + :param int flags: Additional flags (bit0: file append mode). + :return: False if logfile not created otherwise True. + :rtype: bool + """ + return UcanSetDebugMode(level, filename, flags) + + @staticmethod + def get_can_status_message(can_status): + """ + Converts a given CAN status value to the appropriate message string. + + :param can_status: CAN status value from method :meth:`get_status` (see enum :class:`CanStatus`) + :return: Status message string. + :rtype: str + """ + status_msgs = { + CanStatus.CANERR_TXMSGLOST: "Transmit message lost", + CanStatus.CANERR_MEMTEST: "Memory test failed", + CanStatus.CANERR_REGTEST: "Register test failed", + CanStatus.CANERR_QXMTFULL: "Transmit queue is full", + CanStatus.CANERR_QOVERRUN: "Receive queue overrun", + CanStatus.CANERR_QRCVEMPTY: "Receive queue is empty", + CanStatus.CANERR_BUSOFF: "Bus Off", + CanStatus.CANERR_BUSHEAVY: "Error Passive", + CanStatus.CANERR_BUSLIGHT: "Warning Limit", + CanStatus.CANERR_OVERRUN: "Rx-buffer is full", + CanStatus.CANERR_XMTFULL: "Tx-buffer is full", + } + return "OK" if can_status == CanStatus.CANERR_OK \ + else ", ".join(msg for status, msg in status_msgs.items() if can_status & status) + + @staticmethod + def get_baudrate_message(baudrate): + """ + Converts a given baud rate value for GW-001/GW-002 to the appropriate message string. + + :param Baudrate baudrate: + Bus Timing Registers, BTR0 in high order byte and BTR1 in low order byte + (see enum :class:`Baudrate`) + :return: Baud rate message string. + :rtype: str + """ + baudrate_msgs = { + Baudrate.BAUD_AUTO: "auto baudrate", + Baudrate.BAUD_10kBit: "10 kBit/sec", + Baudrate.BAUD_20kBit: "20 kBit/sec", + Baudrate.BAUD_50kBit: "50 kBit/sec", + Baudrate.BAUD_100kBit: "100 kBit/sec", + Baudrate.BAUD_125kBit: "125 kBit/sec", + Baudrate.BAUD_250kBit: "250 kBit/sec", + Baudrate.BAUD_500kBit: "500 kBit/sec", + Baudrate.BAUD_800kBit: "800 kBit/sec", + Baudrate.BAUD_1MBit: "1 MBit/s", + Baudrate.BAUD_USE_BTREX: "BTR Ext is used", + } + return baudrate_msgs.get(baudrate, "BTR is unknown (user specific)") + + @staticmethod + def get_baudrate_ex_message(baudrate_ex): + """ + Converts a given baud rate value for systec USB-CANmoduls to the appropriate message string. + + :param BaudrateEx baudrate_ex: Bus Timing Registers (see enum :class:`BaudrateEx`) + :return: Baud rate message string. + :rtype: str + """ + baudrate_ex_msgs = { + Baudrate.BAUDEX_AUTO: "auto baudrate", + Baudrate.BAUDEX_10kBit: "10 kBit/sec", + Baudrate.BAUDEX_SP2_10kBit: "10 kBit/sec", + Baudrate.BAUDEX_20kBit: "20 kBit/sec", + Baudrate.BAUDEX_SP2_20kBit: "20 kBit/sec", + Baudrate.BAUDEX_50kBit: "50 kBit/sec", + Baudrate.BAUDEX_SP2_50kBit: "50 kBit/sec", + Baudrate.BAUDEX_100kBit: "100 kBit/sec", + Baudrate.BAUDEX_SP2_100kBit: "100 kBit/sec", + Baudrate.BAUDEX_125kBit: "125 kBit/sec", + Baudrate.BAUDEX_SP2_125kBit: "125 kBit/sec", + Baudrate.BAUDEX_250kBit: "250 kBit/sec", + Baudrate.BAUDEX_SP2_250kBit: "250 kBit/sec", + Baudrate.BAUDEX_500kBit: "500 kBit/sec", + Baudrate.BAUDEX_SP2_500kBit: "500 kBit/sec", + Baudrate.BAUDEX_800kBit: "800 kBit/sec", + Baudrate.BAUDEX_SP2_800kBit: "800 kBit/sec", + Baudrate.BAUDEX_1MBit: "1 MBit/s", + Baudrate.BAUDEX_SP2_1MBit: "1 MBit/s", + Baudrate.BAUDEX_USE_BTR01: "BTR0/BTR1 is used", + } + return baudrate_ex_msgs.get(baudrate_ex, "BTR is unknown (user specific)") + + @staticmethod + def get_product_code_message(product_code): + product_code_msgs = { + ProductCode.PRODCODE_PID_GW001: "GW-001", + ProductCode.PRODCODE_PID_GW002: "GW-002", + ProductCode.PRODCODE_PID_MULTIPORT: "Multiport CAN-to-USB G3", + ProductCode.PRODCODE_PID_BASIC: "USB-CANmodul1 G3", + ProductCode.PRODCODE_PID_ADVANCED: "USB-CANmodul2 G3", + ProductCode.PRODCODE_PID_USBCAN8: "USB-CANmodul8 G3", + ProductCode.PRODCODE_PID_USBCAN16: "USB-CANmodul16 G3", + ProductCode.PRODCODE_PID_RESERVED3: "Reserved", + ProductCode.PRODCODE_PID_ADVANCED_G4: "USB-CANmodul2 G4", + ProductCode.PRODCODE_PID_BASIC_G4: "USB-CANmodul1 G4", + ProductCode.PRODCODE_PID_RESERVED1: "Reserved", + ProductCode.PRODCODE_PID_RESERVED2: "Reserved", + } + return product_code_msgs.get(product_code & PRODCODE_MASK_PID, "Product code is unknown") + + @classmethod + def convert_to_major_ver(cls, version): + """ + Converts the a version number into the major version. + + :param int version: Version number to be converted. + :return: Major version. + :rtype: int + """ + return version & 0xFF + + @classmethod + def convert_to_minor_ver(cls, version): + """ + Converts the a version number into the minor version. + + :param int version: Version number to be converted. + :return: Minor version. + :rtype: int + """ + return (version & 0xFF00) >> 8 + + @classmethod + def convert_to_release_ver(cls, version): + """ + Converts the a version number into the release version. + + :param int version: Version number to be converted. + :return: Release version. + :rtype: int + """ + return (version & 0xFFFF0000) >> 16 + + @classmethod + def check_version_is_equal_or_higher(cls, version, cmp_major, cmp_minor): + """ + Checks if the version is equal or higher than a specified value. + + :param int version: Version number to be checked. + :param int cmp_major: Major version to be compared with. + :param int cmp_minor: Minor version to be compared with. + :return: True if equal or higher, otherwise False. + :rtype: bool + """ + return (cls.convert_to_major_ver(version) > cmp_major) or \ + (cls.convert_to_major_ver(version) == cmp_major and cls.convert_to_minor_ver(version) >= cmp_minor) + + @classmethod + def check_is_systec(cls, hw_info_ex): + """ + Checks whether the module is a systec USB-CANmodul. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is a systec USB-CANmodul, otherwise False. + :rtype: bool + """ + return (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) >= ProductCode.PRODCODE_PID_MULTIPORT + + @classmethod + def check_is_G4(cls, hw_info_ex): + """ + Checks whether the module is an USB-CANmodul of fourth generation (G4). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is an USB-CANmodul G4, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_G4 + + @classmethod + def check_is_G3(cls, hw_info_ex): + """ + Checks whether the module is an USB-CANmodul of third generation (G3). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is an USB-CANmodul G3, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and not cls.check_is_G4(hw_info_ex) + + @classmethod + def check_support_cyclic_msg(cls, hw_info_ex): + """ + Checks whether the module supports automatically transmission of cyclic CAN messages. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support cyclic CAN messages, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and \ + cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 6) + + @classmethod + def check_support_two_channel(cls, hw_info_ex): + """ + Checks whether the module supports two CAN channels (at logical device). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module (logical device) does support two CAN channels, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and (hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA) + + @classmethod + def check_support_term_resistor(cls, hw_info_ex): + """ + Checks whether the module supports a termination resistor at the CAN bus. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a termination resistor. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_TERM + + @classmethod + def check_support_user_port(cls, hw_info_ex): + """ + Checks whether the module supports a user I/O port. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module supports a user I/O port, otherwise False. + :rtype: bool + """ + return ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_BASIC) \ + and ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_RESERVED1) \ + and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + + @classmethod + def check_support_rb_user_port(cls, hw_info_ex): + """ + Checks whether the module supports a user I/O port including read back feature. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a user I/O port including the read back feature, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_RBUSER + + @classmethod + def check_support_rb_can_port(cls, hw_info_ex): + """ + Checks whether the module supports a CAN I/O port including read back feature. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a CAN I/O port including the read back feature, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_RBCAN + + @classmethod + def check_support_ucannet(cls, hw_info_ex): + """ + Checks whether the module supports the usage of USB-CANnetwork driver. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support the usage of the USB-CANnetwork driver, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and \ + cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 8) + + @classmethod + def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): + """ + Calculates AMR using CAN-ID range as parameter. + + :param bool is_extended: If True parameters from_id and to_id contains 29-bit CAN-ID. + :param int from_id: First CAN-ID which should be received. + :param int to_id: Last CAN-ID which should be received. + :param bool rtr_only: If True only RTR-Messages should be received, and rtr_too will be ignored. + :param bool rtr_too: If True CAN data frames and RTR-Messages should be received. + :return: Value for AMR. + :rtype: int + """ + return (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) if is_extended else \ + (((from_id ^ to_id) << 21) | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF)) + + @classmethod + def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): + """ + Calculates ACR using CAN-ID range as parameter. + + :param bool is_extended: If True parameters from_id and to_id contains 29-bit CAN-ID. + :param int from_id: First CAN-ID which should be received. + :param int to_id: Last CAN-ID which should be received. + :param bool rtr_only: If True only RTR-Messages should be received, and rtr_too will be ignored. + :param bool rtr_too: If True CAN data frames and RTR-Messages should be received. + :return: Value for ACR. + :rtype: int + """ + return (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) if is_extended else \ + (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + + def _connect_control(self, event, param, arg): + """ + Is the actual callback function for :meth:`init_hw_connect_control_ex`. + + :param event: + Event (:data:`CbEvent.EVENT_CONNECT`, :data:`CbEvent.EVENT_DISCONNECT` or + :data:`CbEvent.EVENT_FATALDISCON`). + :param param: Additional parameter depending on the event. + - CbEvent.EVENT_CONNECT: always 0 + - CbEvent.EVENT_DISCONNECT: always 0 + - CbEvent.EVENT_FATALDISCON: USB-CAN-Handle of the disconnected module + :param arg: Additional parameter defined with :meth:`init_hardware_ex` (not used in this wrapper class). + """ + log.debug("Event: %s, Param: %s" % (event, param)) + + if event == CbEvent.EVENT_FATALDISCON: + self.fatal_disconnect_event(param) + elif event == CbEvent.EVENT_CONNECT: + self.connect_event() + elif event == CbEvent.EVENT_DISCONNECT: + self.disconnect_event() + + def _callback(self, handle, event, channel, arg): + """ + Is called if a working event occurred. + + :param int handle: USB-CAN-Handle returned by the function :meth:`init_hardware`. + :param int event: Event type. + :param int channel: + CAN channel (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or :data:`Channel.CHANNEL_ANY`). + :param arg: Additional parameter defined with :meth:`init_hardware_ex`. + """ + log.debug("Handle: %s, Event: %s, Channel: %s" % (handle, event, channel)) + + if event == CbEvent.EVENT_INITHW: + self.init_hw_event() + elif event == CbEvent.EVENT_init_can: + self.init_can_event(channel) + elif event == CbEvent.EVENT_RECEIVE: + self.can_msg_received_event(channel) + elif event == CbEvent.EVENT_STATUS: + self.status_event(channel) + elif event == CbEvent.EVENT_DEINIT_CAN: + self.deinit_can_event(channel) + elif event == CbEvent.EVENT_DEINITHW: + self.deinit_hw_event() + + def init_hw_event(self): + """ + Event occurs when an USB-CANmodul has been initialized (see method :meth:`init_hardware`). + + .. note:: To be overridden by subclassing. + """ + pass + + def init_can_event(self, channel): + """ + Event occurs when a CAN interface of an USB-CANmodul has been initialized. + + :param int channel: Specifies the CAN channel which was initialized (see method :meth:`init_can`). + + .. note:: To be overridden by subclassing. + """ + pass + + def can_msg_received_event(self, channel): + """ + Event occurs when at leas one CAN message has been received. + + Call the method :meth:`read_can_msg` to receive the CAN messages. + + :param int channel: Specifies the CAN channel which received CAN messages. + + .. note:: To be overridden by subclassing. + """ + pass + + def status_event(self, channel): + """ + Event occurs when the error status of a module has been changed. + + Call the method :meth:`get_status` to receive the error status. + + :param int channel: Specifies the CAN channel which status has been changed. + + .. note:: To be overridden by subclassing. + """ + pass + + def deinit_can_event(self, channel): + """ + Event occurs when a CAN interface has been deinitialized (see method :meth:`shutdown`). + + :param int channel: Specifies the CAN channel which status has been changed. + + .. note:: To be overridden by subclassing. + """ + pass + + def deinit_hw_event(self): + """ + Event occurs when an USB-CANmodul has been deinitialized (see method :meth:`shutdown`). + + .. note:: To be overridden by subclassing. + """ + pass + + def connect_event(self): + """ + Event occurs when a new USB-CANmodul has been connected to the host. + + .. note:: To be overridden by subclassing. + """ + pass + + def disconnect_event(self): + """ + Event occurs when an USB-CANmodul has been disconnected from the host. + + .. note:: To be overridden by subclassing. + """ + pass + + def fatal_disconnect_event(self, device_number): + """ + Event occurs when an USB-CANmodul has been disconnected from the host which was currently initialized. + + No method can be called for this module. + + :param int device_number: The device number which was disconnected. + + .. note:: To be overridden by subclassing. + """ + pass + + +UcanServer._enum_callback_ref = EnumCallback(UcanServer._enum_callback) diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py new file mode 100644 index 000000000..6426e883f --- /dev/null +++ b/can/interfaces/systec/ucanbus.py @@ -0,0 +1,268 @@ +# coding: utf-8 + +import logging +from threading import Event + +from can import BusABC, BusState, Message + +from .constants import * +from .structures import * +from .ucan import UcanServer + +log = logging.getLogger('can.systec') + + +class Ucan(UcanServer): + """ + Wrapper around UcanServer to read messages with timeout using events. + """ + def __init__(self): + super(Ucan, self).__init__() + self._msg_received_event = Event() + + def can_msg_received_event(self, channel): + self._msg_received_event.set() + + def read_can_msg(self, channel, count, timeout): + self._msg_received_event.clear() + if self.get_msg_pending(channel, PendingFlags.PENDING_FLAG_RX_DLL) == 0: + if not self._msg_received_event.wait(timeout): + return None, False + return super(Ucan, self).read_can_msg(channel, 1) + + +class UcanBus(BusABC): + """ + The CAN Bus implemented for the SYSTEC interface. + """ + + BITRATES = { + 10000: Baudrate.BAUD_10kBit, + 20000: Baudrate.BAUD_20kBit, + 50000: Baudrate.BAUD_50kBit, + 100000: Baudrate.BAUD_100kBit, + 125000: Baudrate.BAUD_125kBit, + 250000: Baudrate.BAUD_250kBit, + 500000: Baudrate.BAUD_500kBit, + 800000: Baudrate.BAUD_800kBit, + 1000000: Baudrate.BAUD_1MBit + } + + def __init__(self, channel, can_filters=None, **config): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + Backend Configuration + + :param int bitrate: + Channel bitrate in bit/s. + Default is 500000. + + :param int device_number: + The device number of the USB-CAN. + Valid values: 0 through 254. Special value 255 is reserved to detect the first connected device (should only + be used, in case only one module is connected to the computer). + Default is 255. + + :param can.bus.BusState state: + BusState of the channel. + Default is ACTIVE. + + :param bool receive_own_messages: + If messages transmitted should also be received back. + Default is False. + + :param int rx_buffer_entries: + The maximum number of entries in the receive buffer. + Default is 4096. + + :param int tx_buffer_entries: + The maximum number of entries in the transmit buffer. + Default is 4096. + + :raises ValueError: + If invalid input parameter were passed. + + :raises can.CanError: + If hardware or CAN interface initialization failed. + """ + try: + self._ucan = Ucan() + except Exception: + raise ImportError("The SYSTEC ucan library has not been initialized.") + + self.channel = int(channel) + device_number = int(config.get('device_number', ANY_MODULE)) + + # configuration options + bitrate = config.get('bitrate', 500000) + if bitrate not in self.BITRATES: + raise ValueError("Invalid bitrate {}".format(bitrate)) + + state = config.get('state', BusState.ACTIVE) + if state is BusState.ACTIVE or BusState.PASSIVE: + self._state = state + else: + raise ValueError("BusState must be Active or Passive") + + # get parameters + self._params = { + "mode": Mode.MODE_NORMAL | + (Mode.MODE_TX_ECHO if config.get('receive_own_messages') else 0) | + (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), + "BTR": self.BITRATES[bitrate] + } + # get extra parameters + if config.get("rx_buffer_entries"): + self._params["rx_buffer_entries"] = int(config.get("rx_buffer_entries")) + if config.get("tx_buffer_entries"): + self._params["tx_buffer_entries"] = int(config.get("tx_buffer_entries")) + + self._ucan.init_hardware(device_number=device_number) + self._ucan.init_can(self.channel, **self._params) + hw_info_ex, _, _ = self._ucan.get_hardware_info() + self.channel_info = '%s, S/N %s, CH %s, BTR %s' % ( + self._ucan.get_product_code_message(hw_info_ex.product_code), + hw_info_ex.serial, + self.channel, + self._ucan.get_baudrate_message(self.BITRATES[bitrate]) + ) + super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **config) + + def _recv_internal(self, timeout): + message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) + if not message: + return None, False + + msg = Message(timestamp=float(message[0].time) / 1000.0, + is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), + extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), + arbitration_id=message[0].id, + dlc=len(message[0].data), + data=message[0].data) + return msg, self._is_filtered + + def send(self, msg, timeout=None): + """ + Sends one CAN message. + + When a transmission timeout is set the firmware tries to send + a message within this timeout. If it could not be sent the firmware sets + the "auto delete" state. Within this state all transmit CAN messages for + this channel will be deleted automatically for not blocking the other channel. + + :param can.Message msg: + The CAN message. + + :param float timeout: + Transmit timeout in seconds (value 0 switches off the "auto delete") + + :raises can.CanError: + If the message could not be sent. + + """ + if timeout is not None and timeout >= 0: + self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) + + message = CanMsg(msg.arbitration_id, + MsgFrameFormat.MSG_FF_STD | + (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) | + (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), + msg.data) + self._ucan.write_can_msg(self.channel, [message]) + + @staticmethod + def _detect_available_configs(): + configs = [] + try: + for index, is_used, hw_info_ex, init_info in Ucan.enumerate_hardware(): + configs.append({'interface': 'systec', + 'channel': Channel.CHANNEL_CH0, + 'device_number': hw_info_ex.device_number}) + if Ucan.check_support_two_channel(hw_info_ex): + configs.append({'interface': 'systec', + 'channel': Channel.CHANNEL_CH1, + 'device_number': hw_info_ex.device_number}) + except: + log.warning("The SYSTEC ucan library has not been initialized.") + return configs + + def _apply_filters(self, filters): + if filters and len(filters) == 1: + can_id = filters[0]['can_id'] + can_mask = filters[0]['can_mask'] + self._ucan.set_acceptance(self.channel, can_mask, can_id) + self._is_filtered = True + log.info('Hardware filtering on ID 0x%X, mask 0x%X', can_id, can_mask) + else: + self._ucan.set_acceptance(self.channel) + self._is_filtered = False + log.info('Hardware filtering has been disabled') + + def flush_tx_buffer(self): + """ + Flushes the transmit buffer. + + :raises can.CanError: + If flushing of the transmit buffer failed. + """ + log.info('Flushing transmit buffer') + self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) + + @staticmethod + def create_filter(extended, from_id, to_id, rtr_only, rtr_too): + """ + Calculates AMR and ACR using CAN-ID as parameter. + + :param bool extended: + if True parameters from_id and to_id contains 29-bit CAN-ID + + :param int from_id: + first CAN-ID which should be received + + :param int to_id: + last CAN-ID which should be received + + :param bool rtr_only: + if True only RTR-Messages should be received, and rtr_too will be ignored + + :param bool rtr_too: + if True CAN data frames and RTR-Messages should be received + + :return: Returns list with one filter containing a "can_id", a "can_mask" and "extended" key. + """ + return [{ + "can_id": Ucan.calculate_acr(extended, from_id, to_id, rtr_only, rtr_too), + "can_mask": Ucan.calculate_amr(extended, from_id, to_id, rtr_only, rtr_too), + "extended": extended + }] + + @property + def state(self): + return self._state + + @state.setter + def state(self, new_state): + if self._state != BusState.ERROR and (new_state == BusState.ACTIVE or new_state == BusState.PASSIVE): + # deinitialize CAN channel + self._ucan.shutdown(self.channel, False) + # set mode + if new_state == BusState.ACTIVE: + self._params["mode"] &= ~Mode.MODE_LISTEN_ONLY + else: + self._params["mode"] |= Mode.MODE_LISTEN_ONLY + # reinitialize CAN channel + self._ucan.init_can(self.channel, **self._params) + + def shutdown(self): + """ + Shuts down all CAN interfaces and hardware interface. + """ + try: + self._ucan.shutdown() + except Exception as ex: + log.error(ex) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index fee9e14ab..b395d2336 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -65,7 +65,7 @@ def message_convert_rx(messagerx): msgrx = Message(timestamp=messagerx.timestamp, is_remote_frame=REMOTE_FRAME, - extended_id=ID_TYPE, + is_extended_id=ID_TYPE, is_error_frame=ERROR_FRAME, arbitration_id=messagerx.id, dlc=messagerx.sizeData, @@ -119,6 +119,8 @@ def __init__(self, channel, *args, **kwargs): self.handle = self.can.open(connector.encode('utf-8'), enable_flags) + super(Usb2canBus, self).__init__(channel=channel, *args, **kwargs) + def send(self, msg, timeout=None): tx = message_convert_tx(msg) if timeout: diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7154d5d38..60e423276 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -265,7 +265,7 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), is_remote_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_RTR), is_error_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EF), is_fd=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EDL), @@ -292,7 +292,7 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), is_remote_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME), is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), is_fd=False, diff --git a/can/io/asc.py b/can/io/asc.py index 3feb6755c..3ed50f04a 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -19,6 +19,7 @@ from ..util import channel2int from .generic import BaseIOHandler + CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF @@ -48,7 +49,6 @@ def _extract_can_id(str_can_id): else: is_extended = False can_id = int(str_can_id, 16) - #logging.debug('ASCReader: _extract_can_id("%s") -> %x, %r', str_can_id, can_id, is_extended) return can_id, is_extended def __iter__(self): @@ -72,12 +72,12 @@ def __iter__(self): except ValueError: pass - if dummy.strip()[0:10] == 'ErrorFrame': + if dummy.strip()[0:10].lower() == 'errorframe': msg = Message(timestamp=timestamp, is_error_frame=True, channel=channel) yield msg - elif not isinstance(channel, int) or dummy.strip()[0:10] == 'Statistic:': + elif not isinstance(channel, int) or dummy.strip()[0:10].lower() == 'statistic:': pass elif dummy[-1:].lower() == 'r': @@ -85,7 +85,7 @@ def __iter__(self): can_id_num, is_extended_id = self._extract_can_id(can_id_str) msg = Message(timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, - extended_id=is_extended_id, + is_extended_id=is_extended_id, is_remote_frame=True, channel=channel) yield msg @@ -111,7 +111,7 @@ def __iter__(self): yield Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, - extended_id=is_extended_id, + is_extended_id=is_extended_id, is_remote_frame=False, dlc=dlc, data=frame, @@ -131,8 +131,8 @@ class ASCWriter(BaseIOHandler, Listener): """ FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" - FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y" - FORMAT_EVENT = "{timestamp: 9.4f} {message}\n" + FORMAT_DATE = "%a %b %m %I:%M:%S.{} %p %Y" + FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" def __init__(self, file, channel=1): """ @@ -146,7 +146,7 @@ def __init__(self, file, channel=1): self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y") + now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -176,13 +176,14 @@ def log_event(self, message, timestamp=None): if not self.header_written: self.last_timestamp = (timestamp or 0.0) self.started = self.last_timestamp - formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(self.last_timestamp)) + mlsec = repr(self.last_timestamp).split('.')[1][:3] + formatted_date = time.strftime(self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp)) self.file.write("Begin Triggerblock %s\n" % formatted_date) self.header_written = True self.log_event("Start of measurement") # caution: this is a recursive call! - # figure out the correct timestamp - if timestamp is None or timestamp < self.last_timestamp: + # Use last known timestamp if unknown + if timestamp is None: timestamp = self.last_timestamp # turn into relative timestamps if necessary diff --git a/can/io/blf.py b/can/io/blf.py index df8f611b0..059da5ec9 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -215,7 +215,7 @@ def __iter__(self): can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), dlc=dlc, data=can_data[:dlc], @@ -227,7 +227,7 @@ def __iter__(self): length = dlc2len(dlc) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), is_fd=bool(fd_flags & EDL), bitrate_switch=bool(fd_flags & BRS), @@ -241,7 +241,7 @@ def __iter__(self): can_data) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, is_error_frame=True, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), arbitration_id=can_id & 0x1FFFFFFF, dlc=dlc, data=can_data[:dlc], diff --git a/can/io/canutils.py b/can/io/canutils.py index 40b9ec2b6..69c0227a4 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -81,7 +81,7 @@ def __iter__(self): msg = Message(timestamp=timestamp, is_error_frame=True) else: msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - extended_id=isExtended, is_remote_frame=isRemoteFrame, + is_extended_id=isExtended, is_remote_frame=isRemoteFrame, dlc=dlc, data=dataBin, channel=channel) yield msg diff --git a/can/io/csv.py b/can/io/csv.py index 92b6fb921..029a2c373 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -19,6 +19,7 @@ from can.listener import Listener from .generic import BaseIOHandler + class CSVWriter(BaseIOHandler, Listener): """Writes a comma separated text file with a line for each message. Includes a header line. @@ -37,13 +38,13 @@ class CSVWriter(BaseIOHandler, Listener): data base64 encoded WzQyLCA5XQ== ================ ======================= =============== - Each line is terminated with a platform specific line seperator. + Each line is terminated with a platform specific line separator. """ def __init__(self, file, append=False): """ - :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in text + :param file: a path-like object or a file-like object to write to. + If this is a file-like object, is has to open in text write mode, not binary write mode. :param bool append: if set to `True` messages are appended to the file and no header line is written, else @@ -99,7 +100,7 @@ def __iter__(self): yield Message( timestamp=float(timestamp), is_remote_frame=(remote == '1'), - extended_id=(extended == '1'), + is_extended_id=(extended == '1'), is_error_frame=(error == '1'), arbitration_id=int(arbitration_id, base=16), dlc=int(dlc), diff --git a/can/io/generic.py b/can/io/generic.py index 050e9f0e5..a61c33a9f 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -26,7 +26,7 @@ def __init__(self, file, mode='rt'): :param file: a path-like object to open a file, a file-like object to be used as a file or `None` to not use a file at all :param str mode: the mode that should be used to open the file, see - :func:`builtin.open`, ignored if *file* is `None` + :func:`open`, ignored if *file* is `None` """ if file is None or (hasattr(file, 'read') and hasattr(file, 'write')): # file is None or some file-like object diff --git a/can/io/logger.py b/can/io/logger.py index cc11579c2..52d2e8d83 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -32,6 +32,8 @@ class Logger(BaseIOHandler, Listener): * .log :class:`can.CanutilsLogWriter` * other: :class:`can.Printer` + The log files may be incomplete until `stop()` is called due to buffering. + .. note:: This class itself is just a dispatcher, and any positional an keyword arguments are passed on to the returned instance. diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 3da3cefe5..a12023a6f 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -64,7 +64,7 @@ def _assemble_message(frame_data): return Message( timestamp=timestamp, is_remote_frame=bool(is_remote), - extended_id=bool(is_extended), + is_extended_id=bool(is_extended), is_error_frame=bool(is_error), arbitration_id=can_id, dlc=dlc, @@ -228,7 +228,7 @@ def _db_writer_thread(self): def stop(self): """Stops the reader an writes all remaining messages to the database. Thus, this - might take a while an block. + might take a while and block. """ BufferedReader.stop(self) self._stop_running_event.set() diff --git a/can/listener.py b/can/listener.py index 6ed04b64b..a91b1dac1 100644 --- a/can/listener.py +++ b/can/listener.py @@ -35,6 +35,8 @@ class Listener(object): # or listener.on_message_received(msg) + # Important to ensure all outputs are flushed + listener.stop() """ __metaclass__ = ABCMeta @@ -59,7 +61,10 @@ def on_error(self, exc): def stop(self): """ - Override to cleanup any open resources. + Stop handling new messages, carry out any final tasks to ensure + data is persisted and cleanup any open resources. + + Concrete implementations override. """ diff --git a/can/message.py b/can/message.py index 97346b366..207ee6da5 100644 --- a/can/message.py +++ b/can/message.py @@ -9,8 +9,10 @@ """ from __future__ import absolute_import, division - + import warnings +from copy import deepcopy +from math import isinf, isnan class Message(object): @@ -53,7 +55,7 @@ def __getattr__(self, key): # can be removed in 4.0 # this method is only called if the attribute was not found elsewhere, like in __slots__ try: - warnings.warn("Custom attributes of messages are deprecated and will be removed in the next major version", DeprecationWarning) + warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) return self._dict[key] except KeyError: raise AttributeError("'message' object has no attribute '{}'".format(key)) @@ -63,26 +65,26 @@ def __setattr__(self, key, value): try: super(Message, self).__setattr__(key, value) except AttributeError: - warnings.warn("Custom attributes of messages are deprecated and will be removed in the next major version", DeprecationWarning) + warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) self._dict[key] = value @property def id_type(self): # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) + warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) return self.is_extended_id @id_type.setter def id_type(self, value): # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) + warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) self.is_extended_id = value def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, is_remote_frame=False, is_error_frame=False, channel=None, dlc=None, data=None, is_fd=False, bitrate_switch=False, error_state_indicator=False, - extended_id=True, + extended_id=None, # deprecated in 3.x, TODO remove in 4.x check=False): """ To create a message object, simply provide any of the below attributes @@ -100,10 +102,15 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, self.timestamp = timestamp self.arbitration_id = arbitration_id + + if extended_id is not None: + # TODO remove in 4.0 + warnings.warn("The extended_id parameter is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) + if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = extended_id + self.is_extended_id = True if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame @@ -162,18 +169,19 @@ def __str__(self): field_strings.append(" " * 24) if (self.data is not None) and (self.data.isalnum()): - try: - field_strings.append("'{}'".format(self.data.decode('utf-8'))) - except UnicodeError: - pass + field_strings.append("'{}'".format(self.data.decode('utf-8', 'replace'))) if self.channel is not None: - field_strings.append("Channel: {}".format(self.channel)) + try: + field_strings.append("Channel: {}".format(self.channel)) + except UnicodeEncodeError: + pass return " ".join(field_strings).strip() def __len__(self): - return len(self.data) + # return the dlc such that it also works on remote frames + return self.dlc def __bool__(self): # For Python 3 @@ -221,7 +229,7 @@ def __copy__(self): new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, - extended_id=self.is_extended_id, + is_extended_id=self.is_extended_id, is_remote_frame=self.is_remote_frame, is_error_frame=self.is_error_frame, channel=self.channel, @@ -238,7 +246,7 @@ def __deepcopy__(self, memo): new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, - extended_id=self.is_extended_id, + is_extended_id=self.is_extended_id, is_remote_frame=self.is_remote_frame, is_error_frame=self.is_error_frame, channel=deepcopy(self.channel, memo), @@ -255,33 +263,47 @@ def _check(self): """Checks if the message parameters are valid. Assumes that the types are already correct. - :raises AssertionError: iff one or more attributes are invalid + :raises ValueError: iff one or more attributes are invalid """ - assert 0.0 <= self.timestamp, "the timestamp may not be negative" + if self.timestamp < 0.0: + raise ValueError("the timestamp may not be negative") + if isinf(self.timestamp): + raise ValueError("the timestamp may not be infinite") + if isnan(self.timestamp): + raise ValueError("the timestamp may not be NaN") - assert not (self.is_remote_frame and self.is_error_frame), \ - "a message cannot be a remote and an error frame at the sane time" + if self.is_remote_frame and self.is_error_frame: + raise ValueError("a message cannot be a remote and an error frame at the sane time") - assert 0 <= self.arbitration_id, "arbitration IDs may not be negative" + if self.arbitration_id < 0: + raise ValueError("arbitration IDs may not be negative") if self.is_extended_id: - assert self.arbitration_id < 0x20000000, "Extended arbitration IDs must be less than 2^29" - else: - assert self.arbitration_id < 0x800, "Normal arbitration IDs must be less than 2^11" + if 0x20000000 <= self.arbitration_id: + raise ValueError("Extended arbitration IDs must be less than 2^29") + elif 0x800 <= self.arbitration_id: + raise ValueError("Normal arbitration IDs must be less than 2^11") - assert 0 <= self.dlc, "DLC may not be negative" + if self.dlc < 0: + raise ValueError("DLC may not be negative") if self.is_fd: - assert self.dlc <= 64, "DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc) - else: - assert self.dlc <= 8, "DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc) + if 64 < self.dlc: + raise ValueError("DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc)) + elif 8 < self.dlc: + raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) - if not self.is_remote_frame: - assert self.dlc == len(self.data), "the length of the DLC and the length of the data must match up" + if self.is_remote_frame: + if self.data is not None and len(self.data) != 0: + raise ValueError("remote frames may not carry any data") + elif self.dlc != len(self.data): + raise ValueError("the DLC and the length of the data must match up for non remote frames") if not self.is_fd: - assert not self.bitrate_switch, "bitrate switch is only allowed for CAN FD frames" - assert not self.error_state_indicator, "error stat indicator is only allowed for CAN FD frames" + if self.bitrate_switch: + raise ValueError("bitrate switch is only allowed for CAN FD frames") + if self.error_state_indicator: + raise ValueError("error stat indicator is only allowed for CAN FD frames") def equals(self, other, timestamp_delta=1.0e-6): """ @@ -299,7 +321,7 @@ def equals(self, other, timestamp_delta=1.0e-6): # see https://github.com/hardbyte/python-can/pull/413 for a discussion # on why a delta of 1.0e-6 was chosen return ( - # check for identity first + # check for identity first and finish fast self is other or # then check for equality by value ( diff --git a/can/notifier.py b/can/notifier.py index 45df675dd..7c5f2820d 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -18,8 +18,15 @@ class Notifier(object): def __init__(self, bus, listeners, timeout=1.0, loop=None): - """Manages the distribution of **Messages** from a given bus/buses to a - list of listeners. + """Manages the distribution of :class:`can.Message` instances to listeners. + + Supports multiple busses and listeners. + + .. Note:: + + Remember to call `stop()` after all messages are received as + many listeners carry out flush operations to persist data. + :param can.BusABC bus: A :ref:`bus` or a list of buses to listen to. :param list listeners: An iterable of :class:`~can.Listener` diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index c7d458366..d82ac6bd6 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -87,9 +87,9 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, can_filters=None, *args, **kwargs): + def set_filters(self, filters=None, *args, **kwargs): with self._lock_recv: - return self.__wrapped__.set_filters(can_filters=can_filters, *args, **kwargs) + return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) def flush_tx_buffer(self, *args, **kwargs): with self._lock_send: diff --git a/can/viewer.py b/can/viewer.py index 7b323e559..316d3e3e4 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,6 +1,20 @@ # coding: utf-8 # -# Copyright (C) 2018 Kristian Sloth Lauszus. All rights reserved. +# Copyright (C) 2018 Kristian Sloth Lauszus. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Contact information # ------------------- @@ -11,18 +25,26 @@ from __future__ import absolute_import, print_function import argparse -import can -import curses import os import struct import sys import time - -from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE +import logging from typing import Dict, List, Tuple, Union - +import can from can import __version__ +logger = logging.getLogger('can.serial') + +try: + import curses + from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE +except ImportError: + # Probably on windows + logger.warning("You won't be able to use the viewer program without " + "curses installed!") + curses = None + class CanViewer: @@ -352,8 +374,8 @@ def parse_args(args): '\nNote that the IDs are always interpreted as hex values.' '\nAn optional conversion from integers to real units can be given' '\nas additional arguments. In order to convert from raw integer' - '\nvalues the values are multiplied with the corresponding scaling value,' - '\nsimilarly the values are divided by the scaling value in order' + '\nvalues the values are divided with the corresponding scaling value,' + '\nsimilarly the values are multiplied by the scaling value in order' '\nto convert from real units to raw integer values.' '\nFx lets say the uint8_t needs no conversion, but the uint16 and the uint32_t' '\nneeds to be divided by 10 and 100 respectively:' @@ -370,11 +392,11 @@ def parse_args(args): metavar='{:,:::...:,file.txt}', nargs=argparse.ONE_OR_MORE, default='') - optional.add_argument('-f', '--filter', help='R|Comma separated CAN filters for the given CAN interface:' + optional.add_argument('-f', '--filter', help='R|Space separated CAN filters for the given CAN interface:' '\n : (matches when & mask == can_id & mask)' '\n ~ (matches when & mask != can_id & mask)' - '\nFx to show only frames with ID 0x100 to 0x103:' - '\n python -m can.viewer -f 100:7FC' + '\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:' + '\n python -m can.viewer -f 100:7FC 200:7F0' '\nNote that the ID and mask are alway interpreted as hex values', metavar='{:,~}', nargs=argparse.ONE_OR_MORE, default='') diff --git a/doc/api.rst b/doc/api.rst index 8e657bd3c..640f61e2d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -17,19 +17,16 @@ A form of CAN interface is also required. listeners asyncio bcm + internal-api Utilities --------- -.. automodule:: can.util - :members: .. automethod:: can.detect_available_configs - - .. _notifier: Notifier diff --git a/doc/asyncio.rst b/doc/asyncio.rst index f5bd7771b..cd8d65de5 100644 --- a/doc/asyncio.rst +++ b/doc/asyncio.rst @@ -4,8 +4,9 @@ Asyncio support =============== The :mod:`asyncio` module built into Python 3.4 and later can be used to write -asynchronos code in a single thread. This library supports receiving messages -asynchronosly in an event loop using the :class:`can.Notifier` class. +asynchronous code in a single thread. This library supports receiving messages +asynchronously in an event loop using the :class:`can.Notifier` class. + There will still be one thread per CAN bus but the user application will execute entirely in the event loop, allowing simpler concurrency without worrying about threading issues. Interfaces that have a valid file descriptor will however be diff --git a/doc/bus.rst b/doc/bus.rst index 0b8814c8f..5c1e95606 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -30,6 +30,7 @@ API :members: :undoc-members: + .. automethod:: __iter__ Transmitting '''''''''''' diff --git a/doc/configuration.rst b/doc/configuration.rst index 9297ab15c..dda2ace2a 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -57,7 +57,7 @@ The configuration file sets the default interface and channel: bitrate = -The configuration can also contain additional sections: +The configuration can also contain additional sections (or context): :: @@ -81,8 +81,8 @@ The configuration can also contain additional sections: from can.interfaces.interface import Bus - hs_bus = Bus(config_section='HS') - ms_bus = Bus(config_section='MS') + hs_bus = Bus(context='HS') + ms_bus = Bus(context='MS') Environment Variables --------------------- diff --git a/doc/development.rst b/doc/development.rst index 118c2a3cc..57864753f 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -35,7 +35,7 @@ These steps are a guideline on how to add a new backend to python-can. - Create a module (either a ``*.py`` or an entire subdirectory depending on the complexity) inside ``can.interfaces`` - Implement the central part of the backend: the bus class that extends - :class:`can.BusABC`. See below for more info on this one! + :class:`can.BusABC`. See :ref:`businternals` for more info on this one! - Register your backend bus class in ``can.interface.BACKENDS`` and ``can.interfaces.VALID_INTERFACES`` in ``can.interfaces.__init__.py``. - Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add @@ -44,53 +44,6 @@ These steps are a guideline on how to add a new backend to python-can. - Add tests in ``test/*`` where appropriate. -About the ``BusABC`` class --------------------------- - -Concrete implementations *have to* implement the following: - * :meth:`~can.BusABC.send` to send individual messages - * :meth:`~can.BusABC._recv_internal` to receive individual messages - (see note below!) - * set the :attr:`~can.BusABC.channel_info` attribute to a string describing - the underlying bus and/or channel - -They *might* implement the following: - * :meth:`~can.BusABC.flush_tx_buffer` to allow discarding any - messages yet to be sent - * :meth:`~can.BusABC.shutdown` to override how the bus should - shut down - * :meth:`~can.BusABC._send_periodic_internal` to override the software based - periodic sending and push it down to the kernel or hardware. - * :meth:`~can.BusABC._apply_filters` to apply efficient filters - to lower level systems like the OS kernel or hardware. - * :meth:`~can.BusABC._detect_available_configs` to allow the interface - to report which configurations are currently available for new - connections. - * :meth:`~can.BusABC.state` property to allow reading and/or changing - the bus state. - -.. note:: - - *TL;DR*: Only override :meth:`~can.BusABC._recv_internal`, - never :meth:`~can.BusABC.recv` directly. - - Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` - directly instead of :meth:`~can.BusABC._recv_internal`, but that has - changed to allow the abstract base class to handle in-software message - filtering as a fallback. All internal interfaces now implement that new - behaviour. Older (custom) interfaces might still be implemented like that - and thus might not provide message filtering: - -This is the entire ABC bus class with all internal methods: - -.. autoclass:: can.BusABC - :private-members: - :special-members: - :noindex: - - -Concrete instances are created by :class:`can.Bus`. - Code Structure -------------- diff --git a/doc/history.rst b/doc/history.rst index 3ffc9bb3b..54193aff2 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -44,6 +44,8 @@ and 2018. The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018. +The CANalyst-II interface was contributed by Shaoyu Meng in 2018. + Support for CAN within Python ----------------------------- diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 794959ee1..7c8253f9e 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -23,6 +23,8 @@ The available interfaces are: interfaces/neovi interfaces/vector interfaces/virtual + interfaces/canalystii + interfaces/systec Additional interfaces can be added via a plugin interface. An external package can register a new interface by using the ``can.interface`` entry point in its setup.py. diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst new file mode 100644 index 000000000..687f61fcc --- /dev/null +++ b/doc/interfaces/canalystii.rst @@ -0,0 +1,14 @@ +CANalyst-II +=========== + +CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by +`ZLG ZHIYUAN Electronics`_. + + +Bus +--- + +.. autoclass:: can.interfaces.canalystii.CANalystIIBus + + +.. _ZLG ZHIYUAN Electronics: http://www.zlg.com/can/can/product/id/42.html diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst index 289300093..a4a51ad09 100644 --- a/doc/interfaces/kvaser.rst +++ b/doc/interfaces/kvaser.rst @@ -10,6 +10,8 @@ Bus --- .. autoclass:: can.interfaces.kvaser.canlib.KvaserBus + :members: + :exclude-members: get_stats Internals @@ -35,3 +37,12 @@ If one filter is requested, this is will be handled by the Kvaser driver. If more than one filter is needed, these will be handled in Python code in the ``recv`` method. If a message does not match any of the filters, ``recv()`` will return None. + + +Custom methods +~~~~~~~~~~~~~~~~~ + +This section contains Kvaser driver specific methods. + + +.. automethod:: can.interfaces.kvaser.canlib.KvaserBus.get_stats diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 7c4f89876..bdd934ca7 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -140,9 +140,9 @@ To spam a bus: """:param id: Spam the bus with messages including the data id.""" bus = can.interface.Bus(channel=channel, bustype=bustype) for i in range(10): - msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], extended_id=False) + msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) - # Issue #3: Need to keep running to ensure the writing threads stay alive. ? + time.sleep(1) producer(10) diff --git a/doc/interfaces/systec.rst b/doc/interfaces/systec.rst new file mode 100644 index 000000000..0aa4d9444 --- /dev/null +++ b/doc/interfaces/systec.rst @@ -0,0 +1,76 @@ +.. _systec: + +SYSTEC interface +================ + +Windows interface for the USBCAN devices supporting up to 2 channels based on the +particular product. There is support for the devices also on Linux through the :doc:`socketcan` interface and for Windows using this +``systec`` interface. + +Installation +------------ + +The interface requires installation of the **USBCAN32.dll** library. Download and install the +driver for specific `SYSTEC `__ device. + +Supported devices +----------------- + +The interface supports following devices: + +- GW-001 (obsolete), +- GW-002 (obsolete), +- Multiport CAN-to-USB G3, +- USB-CANmodul1 G3, +- USB-CANmodul2 G3, +- USB-CANmodul8 G3, +- USB-CANmodul16 G3, +- USB-CANmodul1 G4, +- USB-CANmodul2 G4. + +Bus +--- + +.. autoclass:: can.interfaces.systec.ucanbus.UcanBus + :members: + +Configuration +------------- + +The simplest configuration would be:: + + interface = systec + channel = 0 + +Python-can will search for the first device found if not specified explicitly by the +``device_number`` parameter. The ``interface`` and ``channel`` are the only mandatory +parameters. The interface supports two channels 0 and 1. The maximum number of entries in the receive and transmit buffer can be set by the +parameters ``rx_buffer_entries`` and ``tx_buffer_entries``, with default value 4096 +set for both. + +Optional parameters: + +* ``bitrate`` (default 500000) Channel bitrate in bit/s +* ``device_number`` (default first device) The device number of the USB-CAN +* ``rx_buffer_entries`` (default 4096) The maximum number of entries in the receive buffer +* ``tx_buffer_entries`` (default 4096) The maximum number of entries in the transmit buffer +* ``state`` (default BusState.ACTIVE) BusState of the channel +* ``receive_own_messages`` (default False) If messages transmitted should also be received back + +Internals +--------- + +Message filtering +~~~~~~~~~~~~~~~~~ + +The interface and driver supports only setting of one filter per channel. If one filter +is requested, this is will be handled by the driver itself. If more than one filter is +needed, these will be handled in Python code in the ``recv`` method. If a message does +not match any of the filters, ``recv()`` will return None. + +Periodic tasks +~~~~~~~~~~~~~~ + +The driver supports periodic message sending but without the possibility to set +the interval between messages. Therefore the handling of the periodic messages is done +by the interface using the :class:`~can.broadcastmanager.ThreadBasedCyclicSendTask`. diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index 3a7e291cf..7bf01a64c 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -26,6 +26,7 @@ WINDOWS INSTALL 1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1) 2. Install the appropriate version of `pywin32 `_ (win32com) 3. Download the USB2CAN CANAL DLL from the USB2CAN website. Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install. + Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment. (Written against CANAL DLL version v1.0.6) Interface Layout diff --git a/doc/internal-api.rst b/doc/internal-api.rst new file mode 100644 index 000000000..d07c39c58 --- /dev/null +++ b/doc/internal-api.rst @@ -0,0 +1,84 @@ +Internal API +============ + +Here we document the odds and ends that are more helpful for creating your own interfaces +or listeners but generally shouldn't be required to interact with python-can. + + +.. _businternals: + + +Extending the ``BusABC`` class +------------------------------ + +Concrete implementations **must** implement the following: + * :meth:`~can.BusABC.send` to send individual messages + * :meth:`~can.BusABC._recv_internal` to receive individual messages + (see note below!) + * set the :attr:`~can.BusABC.channel_info` attribute to a string describing + the underlying bus and/or channel + +They **might** implement the following: + * :meth:`~can.BusABC.flush_tx_buffer` to allow discarding any + messages yet to be sent + * :meth:`~can.BusABC.shutdown` to override how the bus should + shut down + * :meth:`~can.BusABC._send_periodic_internal` to override the software based + periodic sending and push it down to the kernel or hardware. + * :meth:`~can.BusABC._apply_filters` to apply efficient filters + to lower level systems like the OS kernel or hardware. + * :meth:`~can.BusABC._detect_available_configs` to allow the interface + to report which configurations are currently available for new + connections. + * :meth:`~can.BusABC.state` property to allow reading and/or changing + the bus state. + +.. note:: + + *TL;DR*: Only override :meth:`~can.BusABC._recv_internal`, + never :meth:`~can.BusABC.recv` directly. + + Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` + directly instead of :meth:`~can.BusABC._recv_internal`, but that has + changed to allow the abstract base class to handle in-software message + filtering as a fallback. All internal interfaces now implement that new + behaviour. Older (custom) interfaces might still be implemented like that + and thus might not provide message filtering: + + + +Concrete instances are usually created by :class:`can.Bus` which takes the users +configuration into account. + + +Bus Internals +~~~~~~~~~~~~~ + +Several methods are not documented in the main :class:`can.BusABC` +as they are primarily useful for library developers as opposed to +library users. This is the entire ABC bus class with all internal +methods: + +.. autoclass:: can.BusABC + :private-members: + :special-members: + :noindex: + + + +IO Utilities +------------ + + +.. automodule:: can.io.generic + :members: + + + +Other Util +---------- + + +.. automodule:: can.util + :members: + diff --git a/doc/listeners.rst b/doc/listeners.rst index 3434f2b3e..975de6fd1 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -24,7 +24,7 @@ and are listed below. Some of them allow messages to be written to files, and the corresponding file readers are also documented here. -.. warning :: +.. note :: Please note that writing and the reading a message might not always yield a completely unchanged message again, since some properties are not (yet) diff --git a/doc/message.rst b/doc/message.rst index fcb5e5e1a..921748cb9 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -43,7 +43,7 @@ Message (either 2\ :sup:`11` - 1 for 11-bit IDs, or 2\ :sup:`29` - 1 for 29-bit identifiers). - >>> print(Message(extended_id=False, arbitration_id=100)) + >>> print(Message(is_extended_id=False, arbitration_id=100)) Timestamp: 0.000000 ID: 0064 S DLC: 0 @@ -105,15 +105,15 @@ Message This flag controls the size of the :attr:`~can.Message.arbitration_id` field. Previously this was exposed as `id_type`. - >>> print(Message(extended_id=False)) + >>> print(Message(is_extended_id=False)) Timestamp: 0.000000 ID: 0000 S DLC: 0 - >>> print(Message(extended_id=True)) + >>> print(Message(is_extended_id=True)) Timestamp: 0.000000 ID: 00000000 X DLC: 0 .. note:: - The :meth:`Message.__init__` argument ``extended_id`` has been deprecated in favor of + The initializer argument and attribute ``extended_id`` has been deprecated in favor of ``is_extended_id``, but will continue to work for the ``3.x`` release series. @@ -129,7 +129,7 @@ Message .. attribute:: is_remote_frame - :type: boolean + :type: bool This boolean attribute indicates if the message is a remote frame or a data frame, and modifies the bit in the CAN message's flags field indicating this. diff --git a/examples/cyclic.py b/examples/cyclic.py index 508440041..021af14bf 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -26,7 +26,7 @@ def simple_periodic_send(bus): Sleeps for 2 seconds then stops the task. """ print("Starting to send a message every 200ms for 2s") - msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], extended_id=False) + msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False) task = bus.send_periodic(msg, 0.20) assert isinstance(task, can.CyclicSendTaskABC) time.sleep(2) @@ -36,7 +36,7 @@ def simple_periodic_send(bus): def limited_periodic_send(bus): print("Starting to send a message every 200ms for 1s") - msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], extended_id=True) + msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True) task = bus.send_periodic(msg, 0.20, 1, store_task=False) if not isinstance(task, can.LimitedDurationCyclicSendTaskABC): print("This interface doesn't seem to support a ") @@ -107,7 +107,7 @@ def test_periodic_send_with_modifying_data(bus): if __name__ == "__main__": - reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], extended_id=False) + reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False) for interface, channel in [ ('socketcan', 'vcan0'), diff --git a/examples/send_one.py b/examples/send_one.py index ebf0d1790..67fddf437 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -24,7 +24,7 @@ def send_one(): msg = can.Message(arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4, 1], - extended_id=True) + is_extended_id=True) try: bus.send(msg) diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index 86ee7f5ed..cf7e1f8e3 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -17,8 +17,8 @@ can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] bus.set_filters(can_filters) notifier = can.Notifier(bus, [can.Printer()]) - bus.send(can.Message(arbitration_id=1, extended_id=True)) - bus.send(can.Message(arbitration_id=2, extended_id=True)) - bus.send(can.Message(arbitration_id=1, extended_id=False)) + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) time.sleep(10) diff --git a/setup.py b/setup.py index a40b01268..c4127baa0 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,8 @@ 'pytest-cov~=2.5', 'codecov~=2.0', 'future', - 'six' + 'six', + 'hypothesis' ] + extras_require['serial'] extras_require['test'] = tests_require @@ -100,7 +101,7 @@ python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'wrapt~=1.10', - 'typing', + 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], extras_require=extras_require, diff --git a/test/back2back_test.py b/test/back2back_test.py index 25629bc0e..800ffce9e 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -13,6 +13,7 @@ from multiprocessing.dummy import Pool as ThreadPool import pytest +import random import can @@ -93,26 +94,26 @@ def test_timestamp(self): 'But measured {}'.format(delta_time)) def test_standard_message(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x100, data=[1, 2, 3, 4, 5, 6, 7, 8]) self._send_and_receive(msg) def test_extended_message(self): - msg = can.Message(extended_id=True, + msg = can.Message(is_extended_id=True, arbitration_id=0x123456, data=[10, 11, 12, 13, 14, 15, 16, 17]) self._send_and_receive(msg) def test_remote_message(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x200, is_remote_frame=True, dlc=4) self._send_and_receive(msg) def test_dlc_less_than_eight(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) @@ -120,7 +121,7 @@ def test_dlc_less_than_eight(self): @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): msg = can.Message(is_fd=True, - extended_id=True, + is_extended_id=True, arbitration_id=0x56789, data=[0xff] * 64) self._send_and_receive(msg) @@ -129,7 +130,7 @@ def test_fd_message(self): def test_fd_message_with_brs(self): msg = can.Message(is_fd=True, bitrate_switch=True, - extended_id=True, + is_extended_id=True, arbitration_id=0x98765, data=[0xff] * 48) self._send_and_receive(msg) @@ -168,8 +169,6 @@ def test_broadcast_channel(self): class TestThreadSafeBus(Back2BackTestCase): - """Does some testing that is better than nothing. - """ def setUp(self): self.bus1 = can.ThreadSafeBus(channel=self.CHANNEL_1, @@ -190,7 +189,8 @@ def test_concurrent_writes(self): message = can.Message( arbitration_id=0x123, - extended_id=True, + channel=self.CHANNEL_1, + is_extended_id=True, timestamp=121334.365, data=[254, 255, 1, 2] ) @@ -200,12 +200,57 @@ def sender(msg): self.bus1.send(msg) def receiver(_): - result = self.bus2.recv(timeout=2.0) - self.assertIsNotNone(result) - self.assertEqual(result, message) + return self.bus2.recv(timeout=2.0) sender_pool.map_async(sender, workload) - receiver_pool.map_async(receiver, len(workload) * [None]) + for msg in receiver_pool.map(receiver, len(workload) * [None]): + self.assertIsNotNone(msg) + self.assertEqual(message.arbitration_id, msg.arbitration_id) + self.assertTrue(message.equals(msg, timestamp_delta=None)) + + sender_pool.close() + sender_pool.join() + receiver_pool.close() + receiver_pool.join() + + @pytest.mark.timeout(5.0) + def test_filtered_bus(self): + sender_pool = ThreadPool(100) + receiver_pool = ThreadPool(100) + + included_message = can.Message( + arbitration_id=0x123, + channel=self.CHANNEL_1, + is_extended_id=True, + timestamp=121334.365, + data=[254, 255, 1, 2] + ) + excluded_message = can.Message( + arbitration_id=0x02, + channel=self.CHANNEL_1, + is_extended_id=True, + timestamp=121334.300, + data=[1, 2, 3] + ) + workload = 500 * [included_message] + 500 * [excluded_message] + random.shuffle(workload) + + self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xff, "extended": True}]) + + def sender(msg): + self.bus1.send(msg) + + def receiver(_): + return self.bus2.recv(timeout=2.0) + + sender_pool.map_async(sender, workload) + received_msgs = receiver_pool.map(receiver, 500 * [None]) + + for msg in received_msgs: + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, included_message.arbitration_id) + self.assertTrue(included_message.equals(msg, timestamp_delta=None)) + self.assertEqual(len(received_msgs), 500) sender_pool.close() sender_pool.join() diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index ea9321502..35bc045da 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -13,7 +13,7 @@ class ContextManagerTest(unittest.TestCase): def setUp(self): data = [0, 1, 2, 3, 4, 5, 6, 7] - self.msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) + self.msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) def test_open_buses(self): with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: diff --git a/test/data/example_data.py b/test/data/example_data.py index d4b1b877c..dd433dc3c 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -30,7 +30,7 @@ def sort_messages(messages): # List of messages of different types that can be used in tests -TEST_MESSAGES_BASE = [ +TEST_MESSAGES_BASE = sort_messages([ Message( # empty ), @@ -40,11 +40,11 @@ def sort_messages(messages): ), Message( # no data - arbitration_id=0xAB, extended_id=False + arbitration_id=0xAB, is_extended_id=False ), Message( # no data - arbitration_id=0x42, extended_id=True + arbitration_id=0x42, is_extended_id=True ), Message( # no data @@ -75,57 +75,53 @@ def sort_messages(messages): channel="awesome_channel", ), Message( - arbitration_id=0xABCDEF, extended_id=True, + arbitration_id=0xABCDEF, is_extended_id=True, timestamp=TEST_TIME, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, + arbitration_id=0x123, is_extended_id=False, timestamp=TEST_TIME + 42.42, data=[0xff, 0xff] ), Message( - arbitration_id=0xDADADA, extended_id=True, + arbitration_id=0xDADADA, is_extended_id=True, timestamp=TEST_TIME + .165, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, + arbitration_id=0x123, is_extended_id=False, timestamp=TEST_TIME + .365, data=[254, 255] ), Message( - arbitration_id=0x768, extended_id=False, + arbitration_id=0x768, is_extended_id=False, timestamp=TEST_TIME + 3.165 ), -] -TEST_MESSAGES_BASE = sort_messages(TEST_MESSAGES_BASE) +]) -TEST_MESSAGES_REMOTE_FRAMES = [ +TEST_MESSAGES_REMOTE_FRAMES = sort_messages([ Message( - arbitration_id=0xDADADA, extended_id=True, is_remote_frame=True, + arbitration_id=0xDADADA, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + .165, - data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, is_remote_frame=True, + arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + .365, - data=[254, 255] ), Message( - arbitration_id=0x768, extended_id=False, is_remote_frame=True, + arbitration_id=0x768, is_extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + 3.165 ), Message( - arbitration_id=0xABCDEF, extended_id=True, is_remote_frame=True, + arbitration_id=0xABCDEF, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + 7858.67 ), -] -TEST_MESSAGES_REMOTE_FRAMES = sort_messages(TEST_MESSAGES_REMOTE_FRAMES) +]) -TEST_MESSAGES_ERROR_FRAMES = [ +TEST_MESSAGES_ERROR_FRAMES = sort_messages([ Message( is_error_frame=True ), @@ -137,8 +133,7 @@ def sort_messages(messages): is_error_frame=True, timestamp=TEST_TIME + 17.157 ) -] -TEST_MESSAGES_ERROR_FRAMES = sort_messages(TEST_MESSAGES_ERROR_FRAMES) +]) TEST_ALL_MESSAGES = sort_messages(TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + \ @@ -164,4 +159,4 @@ def generate_message(arbitration_id): and a non-extended ID. """ data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) - return Message(arbitration_id=arbitration_id, data=data, extended_id=False, timestamp=TEST_TIME) + return Message(arbitration_id=arbitration_id, data=data, is_extended_id=False, timestamp=TEST_TIME) diff --git a/test/logformats_test.py b/test/logformats_test.py index 039b3f768..76c9cc426 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -352,12 +352,12 @@ def test_read_known_file(self): expected = [ can.Message( timestamp=1.0, - extended_id=False, + is_extended_id=False, arbitration_id=0x64, data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]), can.Message( timestamp=73.0, - extended_id=True, + is_extended_id=True, arbitration_id=0x1FFFFFFF, is_error_frame=True,) ] diff --git a/test/message_helper.py b/test/message_helper.py index 497e5498f..1c139335c 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -1,13 +1,21 @@ #!/usr/bin/env python # coding: utf-8 +""" +This module contains a helper for writing test cases that need to compare messages. +""" + from __future__ import absolute_import, print_function from copy import copy class ComparingMessagesTestCase(object): - """Must be extended by a class also extending a unittest.TestCase. + """ + Must be extended by a class also extending a unittest.TestCase. + + .. note:: This class does not extend unittest.TestCase so it does not get + run as a test itself. """ def __init__(self, allowed_timestamp_delta=0.0, preserves_channel=True): diff --git a/test/network_test.py b/test/network_test.py index cf5acca76..f4163329d 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -55,7 +55,7 @@ def producer(self, ready_event, msg_read): arbitration_id=self.ids[i], is_remote_frame=self.remote_flags[i], is_error_frame=self.error_flags[i], - extended_id=self.extended_flags[i], + is_extended_id=self.extended_flags[i], data=self.data[i] ) #logging.debug("writing message: {}".format(m)) diff --git a/test/notifier_test.py b/test/notifier_test.py index ca462a2ad..3ab257cf7 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # coding: utf-8 + import unittest import time try: diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 70a017937..40a2e8685 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -24,7 +24,7 @@ def __init__(self, *args, **kwargs): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) bus1 = can.interface.Bus(bustype='virtual') bus2 = can.interface.Bus(bustype='virtual') task = bus1.send_periodic(msg, 0.01, 1) @@ -46,7 +46,7 @@ def test_removing_bus_tasks(self): bus = can.interface.Bus(bustype='virtual') tasks = [] for task_i in range(10): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -65,7 +65,7 @@ def test_managed_tasks(self): bus = can.interface.Bus(bustype='virtual', receive_own_messages=True) tasks = [] for task_i in range(3): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 10, store_task=False) tasks.append(task) @@ -91,7 +91,7 @@ def test_stopping_perodic_tasks(self): bus = can.interface.Bus(bustype='virtual') tasks = [] for task_i in range(10): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 173b80d48..3e0bcf396 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -28,6 +28,7 @@ def setUp(self): canlib.canIoCtl = Mock(return_value=0) canlib.kvReadTimer = Mock() canlib.canSetBusParams = Mock() + canlib.canSetBusParamsFd = Mock() canlib.canBusOn = Mock() canlib.canBusOff = Mock() canlib.canClose = Mock() @@ -37,6 +38,8 @@ def setUp(self): canlib.canWriteSync = Mock() canlib.canWrite = self.canWrite canlib.canReadWait = self.canReadWait + canlib.canGetBusStatistics = Mock() + canlib.canRequestBusStatistics = Mock() self.msg = {} self.msg_in_cue = None @@ -100,7 +103,7 @@ def test_send_extended(self): msg = can.Message( arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4], - extended_id=True) + is_extended_id=True) self.bus.send(msg) @@ -113,7 +116,7 @@ def test_send_standard(self): msg = can.Message( arbitration_id=0x321, data=[50, 51], - extended_id=False) + is_extended_id=False) self.bus.send(msg) @@ -130,7 +133,7 @@ def test_recv_extended(self): self.msg_in_cue = can.Message( arbitration_id=0xc0ffef, data=[1, 2, 3, 4, 5, 6, 7, 8], - extended_id=True) + is_extended_id=True) now = time.time() msg = self.bus.recv() @@ -144,7 +147,7 @@ def test_recv_standard(self): self.msg_in_cue = can.Message( arbitration_id=0x123, data=[100, 101], - extended_id=False) + is_extended_id=False) msg = self.bus.recv() self.assertEqual(msg.arbitration_id, 0x123) @@ -160,6 +163,42 @@ def test_available_configs(self): ] self.assertListEqual(configs, expected) + def test_canfd_default_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + can.Bus(channel=0, bustype='kvaser', fd=True) + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0) + + def test_canfd_nondefault_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + data_bitrate = 2000000 + can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + bitrate_constant = canlib.BITRATE_FD[data_bitrate] + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, bitrate_constant, 0, 0, 0) + + def test_canfd_custom_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + data_bitrate = 123456 + can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, data_bitrate, 0, 0, 0) + + def test_bus_get_stats(self): + stats = self.bus.get_stats() + self.assertTrue(canlib.canRequestBusStatistics.called) + self.assertTrue(canlib.canGetBusStatistics.called) + self.assertIsInstance(stats, canlib.structures.BusStatistics) + @staticmethod def canGetNumberOfChannels(count): count._obj.value = 2 diff --git a/test/test_message_class.py b/test/test_message_class.py new file mode 100644 index 000000000..85dbe8560 --- /dev/null +++ b/test/test_message_class.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +import sys +from math import isinf, isnan +from copy import copy, deepcopy + +from hypothesis import given, settings, reproduce_failure +import hypothesis.strategies as st + +from can import Message + + +class TestMessageClass(unittest.TestCase): + """ + This test tries many inputs to the message class constructor and then sanity checks + all methods and ensures that nothing crashes. It also checks whether Message._check() + allows all valid can frames. + """ + + @given( + timestamp=st.floats(min_value=0.0), + arbitration_id=st.integers(), + is_extended_id=st.booleans(), + is_remote_frame=st.booleans(), + is_error_frame=st.booleans(), + channel=st.one_of(st.text(), st.integers()), + dlc=st.integers(min_value=0, max_value=8), + data=st.one_of(st.binary(min_size=0, max_size=8), st.none()), + is_fd=st.booleans(), + bitrate_switch=st.booleans(), + error_state_indicator=st.booleans() + ) + @settings(max_examples=2000) + def test_methods(self, **kwargs): + is_valid = not ( + (not kwargs['is_remote_frame'] and (len(kwargs['data'] or []) != kwargs['dlc'])) or + (kwargs['arbitration_id'] >= 0x800 and not kwargs['is_extended_id']) or + kwargs['arbitration_id'] >= 0x20000000 or + kwargs['arbitration_id'] < 0 or + (kwargs['is_remote_frame'] and kwargs['is_error_frame']) or + (kwargs['is_remote_frame'] and len(kwargs['data'] or []) > 0) or + ((kwargs['bitrate_switch'] or kwargs['error_state_indicator']) and not kwargs['is_fd']) or + isnan(kwargs['timestamp']) or + isinf(kwargs['timestamp']) + ) + + # this should return normally and not throw an exception + message = Message(check=is_valid, **kwargs) + + if kwargs['data'] is None or kwargs['is_remote_frame']: + kwargs['data'] = bytearray() + + if not is_valid and not kwargs['is_remote_frame']: + with self.assertRaises(ValueError): + Message(check=True, **kwargs) + + self.assertGreater(len(str(message)), 0) + self.assertGreater(len(message.__repr__()), 0) + if is_valid: + self.assertEqual(len(message), kwargs['dlc']) + self.assertTrue(bool(message)) + self.assertGreater(len("{}".format(message)), 0) + _ = "{}".format(message) + with self.assertRaises(Exception): + _ = "{somespec}".format(message) + if sys.version_info.major > 2: + self.assertEqual(bytearray(bytes(message)), kwargs['data']) + + # check copies and equalities + if is_valid: + self.assertEqual(message, message) + normal_copy = copy(message) + deep_copy = deepcopy(message) + for other in (normal_copy, deep_copy, message): + self.assertTrue(message.equals(other, timestamp_delta=None)) + self.assertTrue(message.equals(other)) + self.assertTrue(message.equals(other, timestamp_delta=0)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index e08e6acfd..1419a4439 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -14,8 +14,8 @@ from .data.example_data import TEST_ALL_MESSAGES -EXAMPLE_MSG = Message(arbitration_id=0x123, extended_id=True) -HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, extended_id=True) +EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True) +HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) MATCH_EXAMPLE = [{ "can_id": 0x123, diff --git a/test/test_scripts.py b/test/test_scripts.py index 90687ccd7..da3ba1d6c 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -29,7 +29,7 @@ def setUpClass(cls): __metaclass__ = ABCMeta def test_do_commands_exist(self): - """This test calls each scripts once and veifies that the help + """This test calls each scripts once and verifies that the help can be read without any other errors, like the script not being found. """ diff --git a/test/test_slcan.py b/test/test_slcan.py new file mode 100644 index 000000000..29869cb1c --- /dev/null +++ b/test/test_slcan.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# coding: utf-8 +import unittest +import can + + +class slcanTestCase(unittest.TestCase): + + def setUp(self): + self.bus = can.Bus('loop://', bustype='slcan', sleep_after_open=0) + self.serial = self.bus.serialPortOrig + self.serial.read(self.serial.in_waiting) + + def tearDown(self): + self.bus.shutdown() + + def test_recv_extended(self): + self.serial.write(b'T12ABCDEF2AA55\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 2) + self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + + def test_send_extended(self): + msg = can.Message(arbitration_id=0x12ABCDEF, + is_extended_id=True, + data=[0xAA, 0x55]) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'T12ABCDEF2AA55\r') + + def test_recv_standard(self): + self.serial.write(b't4563112233\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x456) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 3) + self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33]) + + def test_send_standard(self): + msg = can.Message(arbitration_id=0x456, + is_extended_id=False, + data=[0x11, 0x22, 0x33]) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b't4563112233\r') + + def test_recv_standard_remote(self): + self.serial.write(b'r1238\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 8) + + def test_send_standard_remote(self): + msg = can.Message(arbitration_id=0x123, + is_extended_id=False, + is_remote_frame=True, + dlc=8) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'r1238\r') + + def test_recv_extended_remote(self): + self.serial.write(b'R12ABCDEF6\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 6) + + def test_send_extended_remote(self): + msg = can.Message(arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_remote_frame=True, + dlc=6) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'R12ABCDEF6\r') + + def test_partial_recv(self): + self.serial.write(b'T12ABCDEF') + msg = self.bus.recv(0) + self.assertIsNone(msg) + + self.serial.write(b'2AA55\rT12') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 2) + self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + + msg = self.bus.recv(0) + self.assertIsNone(msg) + + self.serial.write(b'ABCDEF2AA55\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_systec.py b/test/test_systec.py new file mode 100644 index 000000000..ce5dda4a7 --- /dev/null +++ b/test/test_systec.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +try: + from unittest.mock import Mock, patch +except ImportError: + from mock import Mock, patch + +import can +from can.interfaces.systec import ucan, ucanbus +from can.interfaces.systec.ucan import * + + +class SystecTest(unittest.TestCase): + + def compare_message(self, first, second, msg): + if first.arbitration_id != second.arbitration_id or first.data != second.data or \ + first.is_extended_id != second.is_extended_id: + raise self.failureException(msg) + + def setUp(self): + # add equality function for can.Message + self.addTypeEqualityFunc(can.Message, self.compare_message) + + ucan.UcanInitHwConnectControlEx = Mock() + ucan.UcanInitHardwareEx = Mock() + ucan.UcanInitHardwareEx2 = Mock() + ucan.UcanInitCanEx2 = Mock() + ucan.UcanGetHardwareInfoEx2 = Mock() + ucan.UcanSetAcceptanceEx = Mock() + ucan.UcanDeinitCanEx = Mock() + ucan.UcanDeinitHardware = Mock() + ucan.UcanWriteCanMsgEx = Mock() + ucan.UcanResetCanEx = Mock() + self.bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + + def test_bus_creation(self): + self.assertIsInstance(self.bus, ucanbus.UcanBus) + self.assertTrue(ucan.UcanInitHwConnectControlEx.called) + self.assertTrue(ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called) + self.assertTrue(ucan.UcanInitCanEx2.called) + self.assertTrue(ucan.UcanGetHardwareInfoEx2.called) + self.assertTrue(ucan.UcanSetAcceptanceEx.called) + + def test_bus_shutdown(self): + self.bus.shutdown() + self.assertTrue(ucan.UcanDeinitCanEx.called) + self.assertTrue(ucan.UcanDeinitHardware.called) + + def test_filter_setup(self): + # no filter in the constructor + expected_args = ( + (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + # one filter is handled by the driver + ucan.UcanSetAcceptanceEx.reset_mock() + can_filter = (True, 0x123, 0x123, False, False) + self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter)) + expected_args = ( + (self.bus._ucan._handle, + 0, + ucan.UcanServer.calculate_amr(*can_filter), + ucan.UcanServer.calculate_acr(*can_filter) + ), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + # multiple filters are handled by the bus + ucan.UcanSetAcceptanceEx.reset_mock() + can_filter = ( + (False, 0x8, 0x8, False, False), + (False, 0x9, 0x9, False, False) + ) + self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter[0]) + + ucanbus.UcanBus.create_filter(*can_filter[1])) + expected_args = ( + (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + def test_send_extended(self, mock_write_can_msg): + msg = can.Message( + arbitration_id=0xc0ffee, + data=[0, 25, 0, 1, 3, 1, 4], + is_extended_id=True) + self.bus.send(msg) + + expected_args = ( + (0, [CanMsg(msg.arbitration_id, MsgFrameFormat.MSG_FF_EXT, msg.data)]), + ) + self.assertEqual(mock_write_can_msg.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + def test_send_standard(self, mock_write_can_msg): + msg = can.Message( + arbitration_id=0x321, + data=[50, 51], + is_extended_id=False) + self.bus.send(msg) + + expected_args = ( + (0, [CanMsg(msg.arbitration_id, MsgFrameFormat.MSG_FF_STD, msg.data)]), + ) + self.assertEqual(mock_write_can_msg.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + def test_recv_no_message(self, mock_get_msg_pending): + mock_get_msg_pending.return_value = 0 + self.assertEqual(self.bus.recv(timeout=0.5), None) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + def test_recv_extended(self, mock_read_can_msg, mock_get_msg_pending): + mock_read_can_msg.return_value = [CanMsg(0xc0ffef, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], 0 + mock_get_msg_pending.return_value = 1 + + msg = can.Message( + arbitration_id=0xc0ffef, + data=[1, 2, 3, 4, 5, 6, 7, 8], + is_extended_id=True) + can_msg = self.bus.recv() + self.assertEqual(can_msg, msg) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending): + mock_read_can_msg.return_value = [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], 0 + mock_get_msg_pending.return_value = 1 + + msg = can.Message( + arbitration_id=0x321, + data=[50, 51], + is_extended_id=False) + can_msg = self.bus.recv() + self.assertEqual(can_msg, msg) + + @staticmethod + def test_bus_defaults(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_channel(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=1) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 1, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_bitrate(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_125kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + def test_bus_custom_bitrate(self): + with self.assertRaises(ValueError): + can.Bus(bustype='systec', channel=0, bitrate=123456) + + @staticmethod + def test_receive_own_messages(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, receive_own_messages=True) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_TX_ECHO, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_passive_state(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, state=can.BusState.PASSIVE) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_LISTEN_ONLY, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_rx_buffer_entries(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, rx_buffer_entries=1024) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + 1024, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_tx_buffer_entries(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, tx_buffer_entries=1024) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + 1024 + ) + ) + + def test_flush_tx_buffer(self): + self.bus.flush_tx_buffer() + ucan.UcanResetCanEx.assert_called_once_with(self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_viewer.py b/test/test_viewer.py index 47e5ef7e7..c4b7aa45f 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -1,7 +1,21 @@ #!/usr/bin/python # coding: utf-8 # -# Copyright (C) 2018 Kristian Sloth Lauszus. All rights reserved. +# Copyright (C) 2018 Kristian Sloth Lauszus. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Contact information # ------------------- @@ -140,31 +154,31 @@ def tearDown(self): def test_send(self): # CANopen EMCY data = [1, 2, 3, 4, 5, 6, 7] # Wrong length - msg = can.Message(arbitration_id=0x080 + 1, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x080 + 1, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x080 + 1, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x080 + 1, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # CANopen HEARTBEAT data = [0x05] # Operational - msg = can.Message(arbitration_id=0x700 + 0x7F, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x700 + 0x7F, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Send non-CANopen message data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x101, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Send the same command, but with another data length data = [1, 2, 3, 4, 5, 6] - msg = can.Message(arbitration_id=0x101, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Message with extended id data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x123456, data=data, extended_id=True) + msg = can.Message(arbitration_id=0x123456, data=data, is_extended_id=True) self.can_viewer.bus.send(msg) # self.assertTupleEqual(self.can_viewer.parse_canopen_message(msg), (None, None)) diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index 83a073dc4..77d55d678 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -19,7 +19,7 @@ def test_recv_non_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') data = [0, 1, 2, 3, 4, 5, 6, 7] - msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) + msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) bus_send.send(msg_send) msg_recv = bus_recv.recv() @@ -38,7 +38,7 @@ def test_recv_none(self): def test_recv_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') - msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=[]) + msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[]) bus_send.send(msg_send) msg_recv = bus_recv.recv()