From 7ad6d7e02b2376ef2f4c6ee04e51c05704d042fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20N=C3=B8rgaard?= Date: Tue, 9 May 2017 11:53:40 +0200 Subject: [PATCH 1/4] Introduce CANAL interface * almost just a copy of the usb2can module which is badly named * the usb2can module is for now left unchanged but should be changed to use the new CANAL interface * TODO serial_selector update... --- can/interface.py | 3 +- can/interfaces/__init__.py | 2 +- can/interfaces/canal/__init__.py | 2 + can/interfaces/canal/canalInterface.py | 166 ++++++++++++++++++++++++ can/interfaces/canal/canal_wrapper.py | 147 +++++++++++++++++++++ can/interfaces/canal/serial_selector.py | 37 ++++++ 6 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 can/interfaces/canal/__init__.py create mode 100644 can/interfaces/canal/canalInterface.py create mode 100644 can/interfaces/canal/canal_wrapper.py create mode 100644 can/interfaces/canal/serial_selector.py 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..68af0af20 --- /dev/null +++ b/can/interfaces/canal/canalInterface.py @@ -0,0 +1,166 @@ +# 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 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: + from can.interfaces.canal.serial_selector import serial + deviceID = serial() + + # 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) + + 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..4fea0dd24 --- /dev/null +++ b/can/interfaces/canal/canal_wrapper.py @@ -0,0 +1,147 @@ +# 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: + res = self.__m_dllBasic.CanalOpen(pConfigureStr, flags) + 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..46808852a --- /dev/null +++ b/can/interfaces/canal/serial_selector.py @@ -0,0 +1,37 @@ +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(): + 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 "ED" in string: + # print "Dependent:" + ` objItem.Dependent` + string = string[len(string) - 9:len(string) - 1] + + return string From 01cff5848526a2cd0aa7bfc318c2602541399ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20N=C3=B8rgaard?= Date: Tue, 9 May 2017 13:22:00 +0200 Subject: [PATCH 2/4] make the serial device autodetect work --- can/interfaces/canal/canalInterface.py | 12 +++++++++++- can/interfaces/canal/canal_wrapper.py | 2 ++ can/interfaces/canal/serial_selector.py | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/can/interfaces/canal/canalInterface.py b/can/interfaces/canal/canalInterface.py index 68af0af20..228123423 100644 --- a/can/interfaces/canal/canalInterface.py +++ b/can/interfaces/canal/canalInterface.py @@ -87,6 +87,12 @@ class CanalBus(BusABC): 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. """ @@ -114,8 +120,12 @@ def __init__(self, channel, *args, **kwargs): elif channel is not None: deviceID = channel else: + # autodetect device from can.interfaces.canal.serial_selector import serial - deviceID = serial() + if 'serialMatcher' in kwargs: + deviceID = serial(kwargs["serialMatcher"]) + else: + deviceID = serial() # set baudrate in kb/s from bitrate # (eg:500000 bitrate must be 500) diff --git a/can/interfaces/canal/canal_wrapper.py b/can/interfaces/canal/canal_wrapper.py index 4fea0dd24..d8bf993c9 100644 --- a/can/interfaces/canal/canal_wrapper.py +++ b/can/interfaces/canal/canal_wrapper.py @@ -60,6 +60,8 @@ def __init__(self, dll): def open(self, pConfigureStr, flags): try: + # unicode is not good + pConfigureStr = pConfigureStr.encode('ascii', 'ignore') res = self.__m_dllBasic.CanalOpen(pConfigureStr, flags) return res except: diff --git a/can/interfaces/canal/serial_selector.py b/can/interfaces/canal/serial_selector.py index 46808852a..1cf8a796d 100644 --- a/can/interfaces/canal/serial_selector.py +++ b/can/interfaces/canal/serial_selector.py @@ -21,7 +21,7 @@ def WMIDateStringToDate(dtmDate): return strDateTime -def serial(): +def serial(serialMatcher = "PID_6001"): strComputer = "." objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2") @@ -30,7 +30,7 @@ def serial(): for objItem in colItems: string = objItem.Dependent # find based on beginning of serial - if "ED" in string: + if serialMatcher in string: # print "Dependent:" + ` objItem.Dependent` string = string[len(string) - 9:len(string) - 1] From a731581c8cecff40380faea9583412829457ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20N=C3=B8rgaard?= Date: Tue, 9 May 2017 13:46:10 +0200 Subject: [PATCH 3/4] Canal interface: Raise CanError on various errors: * autodetect device ID failed * CanalOpen failed --- can/interfaces/canal/canalInterface.py | 5 +++++ can/interfaces/canal/canal_wrapper.py | 2 ++ can/interfaces/canal/serial_selector.py | 2 ++ 3 files changed, 9 insertions(+) diff --git a/can/interfaces/canal/canalInterface.py b/can/interfaces/canal/canalInterface.py index 228123423..93ae5b07d 100644 --- a/can/interfaces/canal/canalInterface.py +++ b/can/interfaces/canal/canalInterface.py @@ -127,6 +127,9 @@ def __init__(self, channel, *args, **kwargs): else: deviceID = serial() + if not deviceID: + raise can.CanError("Device ID could not be autodetected") + # set baudrate in kb/s from bitrate # (eg:500000 bitrate must be 500) if 'bitrate' in kwargs: @@ -141,6 +144,8 @@ def __init__(self, channel, *args, **kwargs): 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) diff --git a/can/interfaces/canal/canal_wrapper.py b/can/interfaces/canal/canal_wrapper.py index d8bf993c9..24f8d1e3b 100644 --- a/can/interfaces/canal/canal_wrapper.py +++ b/can/interfaces/canal/canal_wrapper.py @@ -63,6 +63,8 @@ def open(self, pConfigureStr, flags): # 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') diff --git a/can/interfaces/canal/serial_selector.py b/can/interfaces/canal/serial_selector.py index 1cf8a796d..49941838c 100644 --- a/can/interfaces/canal/serial_selector.py +++ b/can/interfaces/canal/serial_selector.py @@ -35,3 +35,5 @@ def serial(serialMatcher = "PID_6001"): string = string[len(string) - 9:len(string) - 1] return string + + return None \ No newline at end of file From bd17b3e83b7fe3dc7962362bf94c98f6cdd7adb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20N=C3=B8rgaard?= Date: Tue, 9 May 2017 14:08:58 +0200 Subject: [PATCH 4/4] Add an example file --- can/interfaces/canal/canalInterface.py | 2 ++ examples/canal_demo.py | 50 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 examples/canal_demo.py diff --git a/can/interfaces/canal/canalInterface.py b/can/interfaces/canal/canalInterface.py index 93ae5b07d..630b3dc50 100644 --- a/can/interfaces/canal/canalInterface.py +++ b/can/interfaces/canal/canalInterface.py @@ -130,6 +130,8 @@ def __init__(self, channel, *args, **kwargs): 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: 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()