diff --git a/can/interface.py b/can/interface.py index 13feed197..03a543487 100644 --- a/can/interface.py +++ b/can/interface.py @@ -16,7 +16,8 @@ 'nican': ('can.interfaces.nican', 'NicanBus'), 'remote': ('can.interfaces.remote', 'RemoteBus'), 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.neovi_api', 'NeoVIBus') + 'neovi': ('can.interfaces.neovi_api', 'NeoVIBus'), + 'canal': ('can.interfaces.canal', 'CanalBus') } class Bus(object): diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 8d4e7a793..d9f3514d7 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -5,4 +5,4 @@ VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', 'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat', - 'nican', 'remote', 'virtual', 'neovi']) + 'nican', 'remote', 'virtual', 'neovi', 'canal']) 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..630b3dc50 --- /dev/null +++ b/can/interfaces/canal/canalInterface.py @@ -0,0 +1,183 @@ +# 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) + + +# TODO: Issue 36 with data being zeros or anything other than 8 must be fixed +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 = 80000000 + + 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, + 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: + self.can.blocking_send(self.handle, byref(tx), int(timeout * 1000)) + else: + self.can.send(self.handle, byref(tx)) + + 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: + rx = message_convert_rx(messagerx) + elif status == 19 or status == 32: + # CANAL_ERROR_RCV_EMPTY or CANAL_ERROR_TIMEOUT + rx = None + else: + log.error('Canal Error %s', status) + rx = None + + return rx + + def shutdown(self): + """Shut down the device safely""" + status = self.can.close(self.handle) 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/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()