diff --git a/can/interface.py b/can/interface.py index 174f03b58..f60061cfa 100644 --- a/can/interface.py +++ b/can/interface.py @@ -21,7 +21,8 @@ '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'), + 'canal': ('can.interfaces.canal', 'CanalBus'), } diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1bd132731..56c7bec39 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- + """ Interfaces contain low level implementations that interact with CAN hardware. """ + from pkg_resources import iter_entry_points VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', 'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat', 'nican', 'iscan', 'vector', 'virtual', 'neovi', - 'slcan']) - + 'slcan', 'canal']) VALID_INTERFACES.update(set([ interface.name for interface in iter_entry_points('python_can.interface') diff --git a/can/interfaces/canal/__init__.py b/can/interfaces/canal/__init__.py new file mode 100644 index 000000000..2e8cb8ad0 --- /dev/null +++ b/can/interfaces/canal/__init__.py @@ -0,0 +1,2 @@ +from can.interfaces.canal.canalInterface import CanalBus +from can.interfaces.canal.canal_wrapper import CanalWrapper diff --git a/can/interfaces/canal/canalInterface.py b/can/interfaces/canal/canalInterface.py new file mode 100644 index 000000000..c805fc4a4 --- /dev/null +++ b/can/interfaces/canal/canalInterface.py @@ -0,0 +1,196 @@ +# this interface is for windows only, otherwise use socketCAN + +import logging + +from can import BusABC, Message +from can.interfaces.canal.canal_wrapper import * + +bootTimeEpoch = 0 +try: + import uptime + import datetime + + bootTimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() +except: + bootTimeEpoch = 0 + +# Set up logging +log = logging.getLogger('can.canal') + + +def format_connection_string(deviceID, baudrate='500'): + """setup the string for the device + + config = deviceID + '; ' + baudrate + """ + return "%s; %s" % (deviceID, baudrate) + + +def message_convert_tx(msg): + messagetx = CanalMsg() + + length = len(msg.data) + messagetx.sizeData = length + + messagetx.id = msg.arbitration_id + + for i in range(length): + messagetx.data[i] = msg.data[i] + + messagetx.flags = 0x80000000 + + if msg.is_error_frame: + messagetx.flags |= IS_ERROR_FRAME + + if msg.is_remote_frame: + messagetx.flags |= IS_REMOTE_FRAME + + if msg.id_type: + messagetx.flags |= IS_ID_TYPE + + return messagetx + + +def message_convert_rx(messagerx): + """convert the message from the CANAL type to pythoncan type""" + ID_TYPE = bool(messagerx.flags & IS_ID_TYPE) + REMOTE_FRAME = bool(messagerx.flags & IS_REMOTE_FRAME) + ERROR_FRAME = bool(messagerx.flags & IS_ERROR_FRAME) + + msgrx = Message(timestamp=messagerx.timestamp / 1000.0, + is_remote_frame=REMOTE_FRAME, + extended_id=ID_TYPE, + is_error_frame=ERROR_FRAME, + arbitration_id=messagerx.id, + dlc=messagerx.sizeData, + data=messagerx.data[:messagerx.sizeData] + ) + + return msgrx + + +class CanalBus(BusABC): + """Interface to a CANAL Bus. + + Note the interface doesn't implement set_filters, or flush_tx_buffer methods. + + :param str channel: + The device's serial number. If not provided, Windows Management Instrumentation + will be used to identify the first such device. The *kwarg* `serial` may also be + used. + + :param str dll: + dll with CANAL API to load + + :param int bitrate: + Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s. + Default is 500 Kbs + + :param str serial (optional) + device serial to use for the CANAL open call + + :param str serialMatcher (optional) + search string for automatic detection of the device serial + + :param int flags: + Flags to directly pass to open function of the CANAL abstraction layer. + """ + + def __init__(self, channel, *args, **kwargs): + + # TODO force specifying dll + if 'dll' in kwargs: + dll = kwargs["dll"] + else: + raise Exception("please specify a CANAL dll to load, e.g. 'usb2can.dll'") + + self.can = CanalWrapper(dll) + + # set flags on the connection + if 'flags' in kwargs: + enable_flags = kwargs["flags"] + + else: + enable_flags = 0x00000008 + + # code to get the serial number of the device + if 'serial' in kwargs: + deviceID = kwargs["serial"] + elif channel is not None: + deviceID = channel + else: + # autodetect device + from can.interfaces.canal.serial_selector import serial + if 'serialMatcher' in kwargs: + deviceID = serial(kwargs["serialMatcher"]) + else: + deviceID = serial() + + if not deviceID: + raise can.CanError("Device ID could not be autodetected") + + self.channel_info = "CANAL device " + deviceID + + # set baudrate in kb/s from bitrate + # (eg:500000 bitrate must be 500) + if 'bitrate' in kwargs: + br = kwargs["bitrate"] + + # max rate is 1000 kbps + baudrate = max(1000, int(br/1000)) + # set default value + else: + baudrate = 500 + + connector = format_connection_string(deviceID, baudrate) + + self.handle = self.can.open(connector, enable_flags) + # print "ostemad" + + + def send(self, msg, timeout=None): + tx = message_convert_tx(msg) + + if timeout: + status = self.can.blocking_send(self.handle, byref(tx), int(timeout * 1000)) + else: + status = self.can.send(self.handle, byref(tx)) + + if status != 0: + raise can.CanError("could not send message: status == {}".format(status)) + + def recv(self, timeout=None): + + messagerx = CanalMsg() + + if timeout == 0: + status = self.can.receive(self.handle, byref(messagerx)) + + else: + time = 0 if timeout is None else int(timeout * 1000) + status = self.can.blocking_receive(self.handle, byref(messagerx), time) + + if status == 0: + # success + return message_convert_rx(messagerx) + + elif status == 19: + # CANAL_ERROR_RCV_EMPTY + log.warn("recv: status: CANAL_ERROR_RCV_EMPTY") + return None + + elif status == 32: + # CANAL_ERROR_TIMEOUT + log.warn("recv: status: CANAL_ERROR_TIMEOUT") + return None + + else: + # another error + raise can.CanError("could not receive message: status == {}".format(status)) + + def shutdown(self): + """Shut down the device safely""" + status = self.can.close(self.handle) + + if status != 0: + raise can.CanError("could not shut down bus: status == {}".format(status)) diff --git a/can/interfaces/canal/canal_wrapper.py b/can/interfaces/canal/canal_wrapper.py new file mode 100644 index 000000000..24f8d1e3b --- /dev/null +++ b/can/interfaces/canal/canal_wrapper.py @@ -0,0 +1,151 @@ +# This wrapper is for windows or direct access via CANAL API. Socket CAN is recommended under Unix/Linux systems +import can +from ctypes import * +from struct import * +import logging + +log = logging.getLogger('can.canal') + +# type definitions +flags = c_ulong +pConfigureStr = c_char_p +handle = c_long +timeout = c_ulong +filter = c_ulong + +# flags mappings +IS_ERROR_FRAME = 4 +IS_REMOTE_FRAME = 2 +IS_ID_TYPE = 1 + + +class CanalStatistics(Structure): + _fields_ = [('ReceiveFrams', c_ulong), + ('TransmistFrams', c_ulong), + ('ReceiveData', c_ulong), + ('TransmitData', c_ulong), + ('Overruns', c_ulong), + ('BusWarnings', c_ulong), + ('BusOff', c_ulong)] + + +stat = CanalStatistics + + +class CanalStatus(Structure): + _fields_ = [('channel_status', c_ulong), + ('lasterrorcode', c_ulong), + ('lasterrorsubcode', c_ulong), + ('lasterrorstr', c_byte * 80)] + + +# data type for the CAN Message +class CanalMsg(Structure): + _fields_ = [('flags', c_ulong), + ('obid', c_ulong), + ('id', c_ulong), + ('sizeData', c_ubyte), + ('data', c_ubyte * 8), + ('timestamp', c_ulong)] + + +class CanalWrapper: + """A low level wrapper around the CANAL library. + """ + def __init__(self, dll): + self.__m_dllBasic = windll.LoadLibrary(dll) + + if self.__m_dllBasic is None: + log.warning('DLL failed to load') + + def open(self, pConfigureStr, flags): + try: + # unicode is not good + pConfigureStr = pConfigureStr.encode('ascii', 'ignore') + res = self.__m_dllBasic.CanalOpen(pConfigureStr, flags) + if res == 0: + raise can.CanError("CanalOpen failed, configure string: " + pConfigureStr) + return res + except: + log.warning('Failed to open') + raise + + def close(self, handle): + try: + res = self.__m_dllBasic.CanalClose(handle) + return res + except: + log.warning('Failed to close') + raise + + def send(self, handle, msg): + try: + res = self.__m_dllBasic.CanalSend(handle, msg) + return res + except: + log.warning('Sending error') + raise can.CanError("Failed to transmit frame") + + def receive(self, handle, msg): + try: + res = self.__m_dllBasic.CanalReceive(handle, msg) + return res + except: + log.warning('Receive error') + raise + + def blocking_send(self, handle, msg, timeout): + try: + res = self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout) + return res + except: + log.warning('Blocking send error') + raise + + def blocking_receive(self, handle, msg, timeout): + try: + res = self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) + return res + except: + log.warning('Blocking Receive Failed') + raise + + def get_status(self, handle, CanalStatus): + try: + res = self.__m_dllBasic.CanalGetStatus(handle, CanalStatus) + return res + except: + log.warning('Get status failed') + raise + + def get_statistics(self, handle, CanalStatistics): + try: + res = self.__m_dllBasic.CanalGetStatistics(handle, CanalStatistics) + return res + except: + log.warning('Get Statistics failed') + raise + + def get_version(self): + try: + res = self.__m_dllBasic.CanalGetVersion() + return res + except: + log.warning('Failed to get version info') + raise + + def get_library_version(self): + try: + res = self.__m_dllBasic.CanalGetDllVersion() + return res + except: + log.warning('Failed to get DLL version') + raise + + def get_vendor_string(self): + try: + res = self.__m_dllBasic.CanalGetVendorString() + return res + except: + log.warning('Failed to get vendor string') + raise diff --git a/can/interfaces/canal/serial_selector.py b/can/interfaces/canal/serial_selector.py new file mode 100644 index 000000000..49941838c --- /dev/null +++ b/can/interfaces/canal/serial_selector.py @@ -0,0 +1,39 @@ +import logging +try: + import win32com.client +except ImportError: + logging.warning("win32com.client module required for usb2can") + raise + + +def WMIDateStringToDate(dtmDate): + if (dtmDate[4] == 0): + strDateTime = dtmDate[5] + '/' + else: + strDateTime = dtmDate[4] + dtmDate[5] + '/' + + if (dtmDate[6] == 0): + strDateTime = strDateTime + dtmDate[7] + '/' + else: + strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + '/' + strDateTime = strDateTime + dtmDate[0] + dtmDate[1] + dtmDate[2] + dtmDate[3] + " " + dtmDate[8] + dtmDate[ + 9] + ":" + dtmDate[10] + dtmDate[11] + ':' + dtmDate[12] + dtmDate[13] + return strDateTime + + +def serial(serialMatcher = "PID_6001"): + strComputer = "." + objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") + objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2") + colItems = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") + + for objItem in colItems: + string = objItem.Dependent + # find based on beginning of serial + if serialMatcher in string: + # print "Dependent:" + ` objItem.Dependent` + string = string[len(string) - 9:len(string) - 1] + + return string + + return None \ No newline at end of file diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 8ff8b162c..8647222d9 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -9,6 +9,7 @@ from can.bus import BusABC from can.message import Message from can import CanError +import can import time boottimeEpoch = 0 @@ -82,7 +83,7 @@ def __init__(self, channel, *args, **kwargs): else: self.channel_info = channel - bitrate = kwargs.get('bitrate', 500000) + bitrate = kwargs.get('bitrate', can.rc.get('bitrate', 500000)) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA diff --git a/examples/canal_demo.py b/examples/canal_demo.py new file mode 100644 index 000000000..0b8a945e8 --- /dev/null +++ b/examples/canal_demo.py @@ -0,0 +1,50 @@ +""" +This demo shows a usage of an Lawicel CANUSB device. +""" + +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import can + +# import logging +# logging.basicConfig(level=logging.DEBUG) + +serialMatcher="PID_6001" +bus = can.interface.Bus(bustype="canal", dll="canusbdrv64.dll", serialMatcher=serialMatcher, bitrate=100000, flags=4) + +# alternative: specify device serial: +# bus = can.interface.Bus(bustype="canal", dll="canusbdrv64.dll", serial = "LW19ZSBR", bitrate=100000, flags=4) + +def send_one(): + msg = can.Message(arbitration_id=0x00c0ffee, + data=[0, 25, 0, 1, 3, 1, 4, 1], + extended_id=True) + try: + bus.send(msg) + print ("Message sent:") + print (msg) + except can.CanError: + print ("ERROR: Message send failure") + +def receive_one(): + print ("Wait for CAN message...") + try: + # blocking receive + msg = bus.recv(timeout=0) + if msg: + print ("Message received:") + print (msg) + else: + print ("ERROR: Unexpected bus.recv reply") + + except can.CanError: + print ("ERROR: Message not received") + +def demo(): + print ("===== CANAL interface demo =====") + print ("Device: {}".format(bus.channel_info)) + send_one() + receive_one() + +if __name__ == '__main__': + demo()