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()