Skip to content

Commit 0b7af59

Browse files
authored
Merge pull request #567 from shedfly/seeedstudio
Adding seeedstudio usb-can-analyzer interface
2 parents a26c209 + 405c996 commit 0b7af59

File tree

5 files changed

+363
-0
lines changed

5 files changed

+363
-0
lines changed

can/interfaces/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"slcan": ("can.interfaces.slcan", "slcanBus"),
2525
"canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
2626
"systec": ("can.interfaces.systec", "UcanBus"),
27+
"seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
2728
}
2829

2930
BACKENDS.update(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# coding: utf-8
2+
3+
"""
4+
"""
5+
6+
from can.interfaces.seeedstudio.seeedstudio import SeeedBus
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# coding: utf-8
2+
3+
"""
4+
To Support the Seeed USB-Can analyzer interface. The device will appear
5+
as a serial port, for example "/dev/ttyUSB0" on Linux machines
6+
or "COM1" on Windows.
7+
https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html
8+
SKU 114991193
9+
"""
10+
11+
import logging
12+
import struct
13+
from time import time
14+
from can import BusABC, Message
15+
16+
logger = logging.getLogger("seeedbus")
17+
18+
try:
19+
import serial
20+
except ImportError:
21+
logger.warning(
22+
"You won't be able to use the serial can backend without "
23+
"the serial module installed!"
24+
)
25+
serial = None
26+
27+
28+
class SeeedBus(BusABC):
29+
"""
30+
Enable basic can communication over a USB-CAN-Analyzer device.
31+
"""
32+
33+
BITRATE = {
34+
1000000: 0x01,
35+
800000: 0x02,
36+
500000: 0x03,
37+
400000: 0x04,
38+
250000: 0x05,
39+
200000: 0x06,
40+
125000: 0x07,
41+
100000: 0x08,
42+
50000: 0x09,
43+
20000: 0x0A,
44+
10000: 0x0B,
45+
5000: 0x0C,
46+
}
47+
48+
FRAMETYPE = {"STD": 0x01, "EXT": 0x02}
49+
50+
OPERATIONMODE = {
51+
"normal": 0x00,
52+
"loopback": 0x01,
53+
"silent": 0x02,
54+
"loopback_and_silent": 0x03,
55+
}
56+
57+
def __init__(
58+
self,
59+
channel,
60+
baudrate=2000000,
61+
timeout=0.1,
62+
frame_type="STD",
63+
operation_mode="normal",
64+
bitrate=500000,
65+
*args,
66+
**kwargs
67+
):
68+
"""
69+
:param str channel:
70+
The serial device to open. For example "/dev/ttyS1" or
71+
"/dev/ttyUSB0" on Linux or "COM1" on Windows systems.
72+
73+
:param baudrate:
74+
The default matches required baudrate
75+
76+
:param float timeout:
77+
Timeout for the serial device in seconds (default 0.1).
78+
79+
:param str frame_type:
80+
STD or EXT, to select standard or extended messages
81+
82+
:param operation_mode
83+
normal, loopback, silent or loopback_and_silent.
84+
85+
:param bitrate
86+
CAN bus bit rate, selected from available list.
87+
88+
"""
89+
self.bit_rate = bitrate
90+
self.frame_type = frame_type
91+
self.op_mode = operation_mode
92+
self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00])
93+
self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00])
94+
if not channel:
95+
raise ValueError("Must specify a serial port.")
96+
97+
self.channel_info = "Serial interface: " + channel
98+
self.ser = serial.Serial(
99+
channel, baudrate=baudrate, timeout=timeout, rtscts=False
100+
)
101+
102+
super(SeeedBus, self).__init__(channel=channel, *args, **kwargs)
103+
self.init_frame()
104+
105+
def shutdown(self):
106+
"""
107+
Close the serial interface.
108+
"""
109+
self.ser.close()
110+
111+
def init_frame(self, timeout=None):
112+
"""
113+
Send init message to setup the device for comms. this is called during
114+
interface creation.
115+
116+
:param timeout:
117+
This parameter will be ignored. The timeout value of the channel is
118+
used instead.
119+
"""
120+
byte_msg = bytearray()
121+
byte_msg.append(0xAA) # Frame Start Byte 1
122+
byte_msg.append(0x55) # Frame Start Byte 2
123+
byte_msg.append(0x12) # Initialization Message ID
124+
byte_msg.append(SeeedBus.BITRATE[self.bit_rate]) # CAN Baud Rate
125+
byte_msg.append(SeeedBus.FRAMETYPE[self.frame_type])
126+
byte_msg.extend(self.filter_id)
127+
byte_msg.extend(self.mask_id)
128+
byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode])
129+
byte_msg.append(0x01) # Follows 'Send once' in windows app.
130+
131+
byte_msg.extend([0x00] * 4) # Manual bitrate config, details unknown.
132+
133+
crc = sum(byte_msg[2:]) & 0xFF
134+
byte_msg.append(crc)
135+
136+
logger.debug("init_frm:\t%s", byte_msg.hex())
137+
self.ser.write(byte_msg)
138+
139+
def flush_buffer(self):
140+
self.ser.flushInput()
141+
142+
def status_frame(self, timeout=None):
143+
"""
144+
Send status request message over the serial device. The device will
145+
respond but details of error codes are unknown but are logged - DEBUG.
146+
147+
:param timeout:
148+
This parameter will be ignored. The timeout value of the channel is
149+
used instead.
150+
"""
151+
byte_msg = bytearray()
152+
byte_msg.append(0xAA) # Frame Start Byte 1
153+
byte_msg.append(0x55) # Frame Start Byte 2
154+
byte_msg.append(0x04) # Status Message ID
155+
byte_msg.append(0x00) # In response packet - Rx error count
156+
byte_msg.append(0x00) # In response packet - Tx error count
157+
158+
byte_msg.extend([0x00] * 14)
159+
160+
crc = sum(byte_msg[2:]) & 0xFF
161+
byte_msg.append(crc)
162+
163+
logger.debug("status_frm:\t%s", byte_msg.hex())
164+
self.ser.write(byte_msg)
165+
166+
def send(self, msg, timeout=None):
167+
"""
168+
Send a message over the serial device.
169+
170+
:param can.Message msg:
171+
Message to send.
172+
173+
:param timeout:
174+
This parameter will be ignored. The timeout value of the channel is
175+
used instead.
176+
"""
177+
178+
byte_msg = bytearray()
179+
byte_msg.append(0xAA)
180+
181+
m_type = 0xC0
182+
if msg.is_extended_id:
183+
m_type += 1 << 5
184+
185+
if msg.is_remote_frame:
186+
m_type += 1 << 4
187+
188+
m_type += msg.dlc
189+
byte_msg.append(m_type)
190+
191+
if msg.is_extended_id:
192+
a_id = struct.pack("<I", msg.arbitration_id)
193+
else:
194+
a_id = struct.pack("<H", msg.arbitration_id)
195+
196+
byte_msg.extend(a_id)
197+
byte_msg.extend(msg.data)
198+
byte_msg.append(0x55)
199+
200+
logger.debug("sending:\t%s", byte_msg.hex())
201+
self.ser.write(byte_msg)
202+
203+
def _recv_internal(self, timeout):
204+
"""
205+
Read a message from the serial device.
206+
207+
:param timeout:
208+
209+
.. warning::
210+
This parameter will be ignored. The timeout value of the
211+
channel is used.
212+
213+
:returns:
214+
Received message and False (because not filtering as taken place).
215+
216+
:rtype:
217+
can.Message, bool
218+
"""
219+
try:
220+
# ser.read can return an empty string
221+
# or raise a SerialException
222+
rx_byte_1 = self.ser.read()
223+
224+
except serial.SerialException:
225+
return None, False
226+
227+
if rx_byte_1 and ord(rx_byte_1) == 0xAA:
228+
rx_byte_2 = ord(self.ser.read())
229+
time_stamp = time()
230+
if rx_byte_2 == 0x55:
231+
status = bytearray([0xAA, 0x55])
232+
status += bytearray(self.ser.read(18))
233+
logger.debug("status resp:\t%s", status.hex())
234+
235+
else:
236+
length = int(rx_byte_2 & 0x0F)
237+
is_extended = bool(rx_byte_2 & 0x20)
238+
is_remote = bool(rx_byte_2 & 0x10)
239+
if is_extended:
240+
s_3_4_5_6 = bytearray(self.ser.read(4))
241+
arb_id = (struct.unpack("<I", s_3_4_5_6))[0]
242+
243+
else:
244+
s_3_4 = bytearray(self.ser.read(2))
245+
arb_id = (struct.unpack("<H", s_3_4))[0]
246+
247+
data = bytearray(self.ser.read(length))
248+
end_packet = ord(self.ser.read())
249+
if end_packet == 0x55:
250+
msg = Message(
251+
timestamp=time_stamp,
252+
arbitration_id=arb_id,
253+
is_extended_id=is_extended,
254+
is_remote_frame=is_remote,
255+
dlc=length,
256+
data=data,
257+
)
258+
logger.debug("recv message: %s", str(msg))
259+
return msg, False
260+
261+
else:
262+
return None, False
263+
264+
return None, None
265+
266+
def fileno(self):
267+
if hasattr(self.ser, "fileno"):
268+
return self.ser.fileno()
269+
# Return an invalid file descriptor on Windows
270+
return -1

doc/interfaces/seeedstudio.rst

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
.. _seeeddoc:
2+
3+
4+
USB-CAN Analyzer
5+
================
6+
...by Seeed Studio
7+
8+
SKU: 114991193
9+
10+
Links:
11+
12+
- https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html
13+
- https://github.com/SeeedDocument/USB-CAN_Analyzer
14+
- https://copperhilltech.com/blog/usbcan-analyzer-usb-to-can-bus-serial-protocol-definition/
15+
16+
^^^^^^^^^^
17+
18+
Installation
19+
------------
20+
This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install an additional packages if not already available:
21+
- pyserial
22+
23+
24+
::
25+
26+
pip3 install python-can[seeedstudio]
27+
28+
29+
^^^^^^^^^^
30+
31+
32+
Interface
33+
---------
34+
35+
::
36+
37+
can.interfaces.seeedstudio.SeeedBus
38+
39+
A bus example::
40+
41+
bus = can.interface.Bus(bustype='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000)
42+
43+
44+
^^^^^^^^^^
45+
46+
Parameters
47+
----------
48+
::
49+
50+
SeeedBus(channel,
51+
baudrate=2000000,
52+
timeout=0.1,
53+
frame_type='STD',
54+
operation_mode='normal',
55+
bitrate=500000)
56+
57+
ChANNEL
58+
The serial port created by the USB device when connected.
59+
60+
TIMEOUT
61+
Only used by the underling serial port, it probably should not be changed. The serial port baudrate=2000000 and rtscts=false are also matched to the device so are not added here.
62+
63+
FRAMETYPE
64+
- "STD"
65+
- "EXT"
66+
67+
OPERATIONMODE
68+
- "normal"
69+
- "loopback"
70+
- "silent"
71+
- "loopback_and_silent"
72+
73+
BITRATE
74+
- 1000000
75+
- 800000
76+
- 500000
77+
- 400000
78+
- 250000
79+
- 200000
80+
- 125000
81+
- 100000
82+
- 50000
83+
- 20000
84+
- 10000
85+
- 5000

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
# Dependencies
2727
extras_require = {
28+
"seeedstudio": ["pyserial>=3.0"],
2829
"serial": ["pyserial~=3.0"],
2930
"neovi": ["python-ics>=2.12", "filelock"],
3031
}

0 commit comments

Comments
 (0)