Skip to content

Commit bf56c58

Browse files
Fix some slcan issues (#447)
Allow partial messages to be received Fix DLC for remote frames Add unit test
1 parent 84aa6b1 commit bf56c58

File tree

2 files changed

+143
-17
lines changed

2 files changed

+143
-17
lines changed

can/interfaces/slcan.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ class slcanBus(BusABC):
4747

4848
_SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds
4949

50+
LINE_TERMINATOR = b'\r'
51+
5052
def __init__(self, channel, ttyBaudrate=115200, bitrate=None,
53+
sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN,
5154
rtscts=False, **kwargs):
5255
"""
5356
:param str channel:
@@ -59,6 +62,8 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None,
5962
Bitrate in bit/s
6063
:param float poll_interval:
6164
Poll interval in seconds when reading messages
65+
:param float sleep_after_open:
66+
Time to wait in seconds after opening serial connection
6267
:param bool rtscts:
6368
turn hardware handshake (RTS/CTS) on and off
6469
"""
@@ -72,7 +77,9 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None,
7277
self.serialPortOrig = serial.serial_for_url(
7378
channel, baudrate=ttyBaudrate, rtscts=rtscts)
7479

75-
time.sleep(self._SLEEP_AFTER_SERIAL_OPEN)
80+
self._buffer = bytearray()
81+
82+
time.sleep(sleep_after_open)
7683

7784
if bitrate is not None:
7885
self.close()
@@ -87,9 +94,7 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None,
8794
bitrate=None, rtscts=False, **kwargs)
8895

8996
def write(self, string):
90-
if not string.endswith('\r'):
91-
string += '\r'
92-
self.serialPortOrig.write(string.encode())
97+
self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR)
9398
self.serialPortOrig.flush()
9499

95100
def open(self):
@@ -107,12 +112,20 @@ def _recv_internal(self, timeout):
107112
extended = False
108113
frame = []
109114

110-
readStr = self.serialPortOrig.read_until(b'\r')
115+
# Read everything that is already available
116+
waiting = self.serialPortOrig.read(self.serialPortOrig.in_waiting)
117+
self._buffer += waiting
111118

112-
if not readStr:
113-
return None, False
114-
else:
115-
readStr = readStr.decode()
119+
# Check if a complete message has been received
120+
pos = self._buffer.find(self.LINE_TERMINATOR)
121+
if pos == -1:
122+
# Keep reading...
123+
self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR)
124+
pos = self._buffer.find(self.LINE_TERMINATOR)
125+
126+
if pos != -1:
127+
readStr = self._buffer[0:pos].decode()
128+
del self._buffer[0:pos+1]
116129
if readStr[0] == 'T':
117130
# extended frame
118131
canId = int(readStr[1:9], 16)
@@ -129,45 +142,46 @@ def _recv_internal(self, timeout):
129142
elif readStr[0] == 'r':
130143
# remote frame
131144
canId = int(readStr[1:4], 16)
145+
dlc = int(readStr[4])
132146
remote = True
133147
elif readStr[0] == 'R':
134148
# remote extended frame
135149
canId = int(readStr[1:9], 16)
150+
dlc = int(readStr[9])
136151
extended = True
137152
remote = True
138153

139154
if canId is not None:
140155
msg = Message(arbitration_id=canId,
141-
extended_id=extended,
156+
is_extended_id=extended,
142157
timestamp=time.time(), # Better than nothing...
143158
is_remote_frame=remote,
144159
dlc=dlc,
145160
data=frame)
146161
return msg, False
147-
else:
148-
return None, False
162+
return None, False
149163

150-
def send(self, msg, timeout=0):
164+
def send(self, msg, timeout=None):
151165
if timeout != self.serialPortOrig.write_timeout:
152166
self.serialPortOrig.write_timeout = timeout
153167

154168
if msg.is_remote_frame:
155169
if msg.is_extended_id:
156-
sendStr = "R%08X0" % (msg.arbitration_id)
170+
sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc)
157171
else:
158-
sendStr = "r%03X0" % (msg.arbitration_id)
172+
sendStr = "r%03X%d" % (msg.arbitration_id, msg.dlc)
159173
else:
160174
if msg.is_extended_id:
161175
sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc)
162176
else:
163177
sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc)
164178

165-
for i in range(0, msg.dlc):
166-
sendStr += "%02X" % msg.data[i]
179+
sendStr += "".join(["%02X" % b for b in msg.data])
167180
self.write(sendStr)
168181

169182
def shutdown(self):
170183
self.close()
184+
self.serialPortOrig.close()
171185

172186
def fileno(self):
173187
if hasattr(self.serialPortOrig, 'fileno'):

test/test_slcan.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
import unittest
4+
import can
5+
6+
7+
class slcanTestCase(unittest.TestCase):
8+
9+
def setUp(self):
10+
self.bus = can.Bus('loop://', bustype='slcan', sleep_after_open=0)
11+
self.serial = self.bus.serialPortOrig
12+
self.serial.read(self.serial.in_waiting)
13+
14+
def tearDown(self):
15+
self.bus.shutdown()
16+
17+
def test_recv_extended(self):
18+
self.serial.write(b'T12ABCDEF2AA55\r')
19+
msg = self.bus.recv(0)
20+
self.assertIsNotNone(msg)
21+
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
22+
self.assertEqual(msg.is_extended_id, True)
23+
self.assertEqual(msg.is_remote_frame, False)
24+
self.assertEqual(msg.dlc, 2)
25+
self.assertSequenceEqual(msg.data, [0xAA, 0x55])
26+
27+
def test_send_extended(self):
28+
msg = can.Message(arbitration_id=0x12ABCDEF,
29+
is_extended_id=True,
30+
data=[0xAA, 0x55])
31+
self.bus.send(msg)
32+
data = self.serial.read(self.serial.in_waiting)
33+
self.assertEqual(data, b'T12ABCDEF2AA55\r')
34+
35+
def test_recv_standard(self):
36+
self.serial.write(b't4563112233\r')
37+
msg = self.bus.recv(0)
38+
self.assertIsNotNone(msg)
39+
self.assertEqual(msg.arbitration_id, 0x456)
40+
self.assertEqual(msg.is_extended_id, False)
41+
self.assertEqual(msg.is_remote_frame, False)
42+
self.assertEqual(msg.dlc, 3)
43+
self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33])
44+
45+
def test_send_standard(self):
46+
msg = can.Message(arbitration_id=0x456,
47+
is_extended_id=False,
48+
data=[0x11, 0x22, 0x33])
49+
self.bus.send(msg)
50+
data = self.serial.read(self.serial.in_waiting)
51+
self.assertEqual(data, b't4563112233\r')
52+
53+
def test_recv_standard_remote(self):
54+
self.serial.write(b'r1238\r')
55+
msg = self.bus.recv(0)
56+
self.assertIsNotNone(msg)
57+
self.assertEqual(msg.arbitration_id, 0x123)
58+
self.assertEqual(msg.is_extended_id, False)
59+
self.assertEqual(msg.is_remote_frame, True)
60+
self.assertEqual(msg.dlc, 8)
61+
62+
def test_send_standard_remote(self):
63+
msg = can.Message(arbitration_id=0x123,
64+
is_extended_id=False,
65+
is_remote_frame=True,
66+
dlc=8)
67+
self.bus.send(msg)
68+
data = self.serial.read(self.serial.in_waiting)
69+
self.assertEqual(data, b'r1238\r')
70+
71+
def test_recv_extended_remote(self):
72+
self.serial.write(b'R12ABCDEF6\r')
73+
msg = self.bus.recv(0)
74+
self.assertIsNotNone(msg)
75+
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
76+
self.assertEqual(msg.is_extended_id, True)
77+
self.assertEqual(msg.is_remote_frame, True)
78+
self.assertEqual(msg.dlc, 6)
79+
80+
def test_send_extended_remote(self):
81+
msg = can.Message(arbitration_id=0x12ABCDEF,
82+
is_extended_id=True,
83+
is_remote_frame=True,
84+
dlc=6)
85+
self.bus.send(msg)
86+
data = self.serial.read(self.serial.in_waiting)
87+
self.assertEqual(data, b'R12ABCDEF6\r')
88+
89+
def test_partial_recv(self):
90+
self.serial.write(b'T12ABCDEF')
91+
msg = self.bus.recv(0)
92+
self.assertIsNone(msg)
93+
94+
self.serial.write(b'2AA55\rT12')
95+
msg = self.bus.recv(0)
96+
self.assertIsNotNone(msg)
97+
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
98+
self.assertEqual(msg.is_extended_id, True)
99+
self.assertEqual(msg.is_remote_frame, False)
100+
self.assertEqual(msg.dlc, 2)
101+
self.assertSequenceEqual(msg.data, [0xAA, 0x55])
102+
103+
msg = self.bus.recv(0)
104+
self.assertIsNone(msg)
105+
106+
self.serial.write(b'ABCDEF2AA55\r')
107+
msg = self.bus.recv(0)
108+
self.assertIsNotNone(msg)
109+
110+
111+
if __name__ == '__main__':
112+
unittest.main()

0 commit comments

Comments
 (0)