Skip to content

Commit 5526e32

Browse files
authored
Fix slcanBus.get_version() and slcanBus.get_serial_number() (#1904)
Co-authored-by: zariiii9003 <zariiii9003@users.noreply.github.com>
1 parent 654a02a commit 5526e32

File tree

3 files changed

+110
-44
lines changed

3 files changed

+110
-44
lines changed

can/interfaces/slcan.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import logging
77
import time
88
import warnings
9-
from typing import Any, Optional, Tuple, Union
9+
from queue import SimpleQueue
10+
from typing import Any, Optional, Tuple, Union, cast
1011

1112
from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message, typechecking
1213
from can.exceptions import (
@@ -131,6 +132,7 @@ def __init__(
131132
timeout=timeout,
132133
)
133134

135+
self._queue: SimpleQueue[str] = SimpleQueue()
134136
self._buffer = bytearray()
135137
self._can_protocol = CanProtocol.CAN_20
136138

@@ -196,7 +198,7 @@ def _read(self, timeout: Optional[float]) -> Optional[str]:
196198
# We read the `serialPortOrig.in_waiting` only once here.
197199
in_waiting = self.serialPortOrig.in_waiting
198200
for _ in range(max(1, in_waiting)):
199-
new_byte = self.serialPortOrig.read(size=1)
201+
new_byte = self.serialPortOrig.read(1)
200202
if new_byte:
201203
self._buffer.extend(new_byte)
202204
else:
@@ -234,7 +236,10 @@ def _recv_internal(
234236
extended = False
235237
data = None
236238

237-
string = self._read(timeout)
239+
if self._queue.qsize():
240+
string: Optional[str] = self._queue.get_nowait()
241+
else:
242+
string = self._read(timeout)
238243

239244
if not string:
240245
pass
@@ -300,7 +305,7 @@ def shutdown(self) -> None:
300305

301306
def fileno(self) -> int:
302307
try:
303-
return self.serialPortOrig.fileno()
308+
return cast(int, self.serialPortOrig.fileno())
304309
except io.UnsupportedOperation:
305310
raise NotImplementedError(
306311
"fileno is not implemented using current CAN bus on this platform"
@@ -321,19 +326,21 @@ def get_version(
321326
int hw_version is the hardware version or None on timeout
322327
int sw_version is the software version or None on timeout
323328
"""
329+
_timeout = serial.Timeout(timeout)
324330
cmd = "V"
325331
self._write(cmd)
326332

327-
string = self._read(timeout)
328-
329-
if not string:
330-
pass
331-
elif string[0] == cmd and len(string) == 6:
332-
# convert ASCII coded version
333-
hw_version = int(string[1:3])
334-
sw_version = int(string[3:5])
335-
return hw_version, sw_version
336-
333+
while True:
334+
if string := self._read(_timeout.time_left()):
335+
if string[0] == cmd:
336+
# convert ASCII coded version
337+
hw_version = int(string[1:3])
338+
sw_version = int(string[3:5])
339+
return hw_version, sw_version
340+
else:
341+
self._queue.put_nowait(string)
342+
if _timeout.expired():
343+
break
337344
return None, None
338345

339346
def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
@@ -345,15 +352,17 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
345352
:return:
346353
:obj:`None` on timeout or a :class:`str` object.
347354
"""
355+
_timeout = serial.Timeout(timeout)
348356
cmd = "N"
349357
self._write(cmd)
350358

351-
string = self._read(timeout)
352-
353-
if not string:
354-
pass
355-
elif string[0] == cmd and len(string) == 6:
356-
serial_number = string[1:-1]
357-
return serial_number
358-
359+
while True:
360+
if string := self._read(_timeout.time_left()):
361+
if string[0] == cmd:
362+
serial_number = string[1:-1]
363+
return serial_number
364+
else:
365+
self._queue.put_nowait(string)
366+
if _timeout.expired():
367+
break
359368
return None

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ exclude = [
122122
"^can/interfaces/neousys",
123123
"^can/interfaces/pcan",
124124
"^can/interfaces/serial",
125-
"^can/interfaces/slcan",
126125
"^can/interfaces/socketcan",
127126
"^can/interfaces/systec",
128127
"^can/interfaces/udp_multicast",

test/test_slcan.py

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python
22

3-
import unittest
4-
from typing import cast
3+
import unittest.mock
4+
from typing import cast, Optional
55

6-
import serial
6+
from serial.serialutil import SerialBase
77

88
import can.interfaces.slcan
99

@@ -21,20 +21,69 @@
2121
TIMEOUT = 0.5 if IS_PYPY else 0.01 # 0.001 is the default set in slcanBus
2222

2323

24+
class SerialMock(SerialBase):
25+
def __init__(self, *args, **kwargs) -> None:
26+
super().__init__(*args, **kwargs)
27+
28+
self._input_buffer = b""
29+
self._output_buffer = b""
30+
31+
def open(self) -> None:
32+
self.is_open = True
33+
34+
def close(self) -> None:
35+
self.is_open = False
36+
self._input_buffer = b""
37+
self._output_buffer = b""
38+
39+
def read(self, size: int = -1, /) -> bytes:
40+
if size > 0:
41+
data = self._input_buffer[:size]
42+
self._input_buffer = self._input_buffer[size:]
43+
return data
44+
return b""
45+
46+
def write(self, b: bytes, /) -> Optional[int]:
47+
self._output_buffer = b
48+
if b == b"N\r":
49+
self.set_input_buffer(b"NA123\r")
50+
elif b == b"V\r":
51+
self.set_input_buffer(b"V1013\r")
52+
return len(b)
53+
54+
def set_input_buffer(self, expected: bytes) -> None:
55+
self._input_buffer = expected
56+
57+
def get_output_buffer(self) -> bytes:
58+
return self._output_buffer
59+
60+
def reset_input_buffer(self) -> None:
61+
self._input_buffer = b""
62+
63+
@property
64+
def in_waiting(self) -> int:
65+
return len(self._input_buffer)
66+
67+
@classmethod
68+
def serial_for_url(cls, *args, **kwargs) -> SerialBase:
69+
return cls(*args, **kwargs)
70+
71+
2472
class slcanTestCase(unittest.TestCase):
73+
@unittest.mock.patch("serial.serial_for_url", SerialMock.serial_for_url)
2574
def setUp(self):
2675
self.bus = cast(
2776
can.interfaces.slcan.slcanBus,
2877
can.Bus("loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT),
2978
)
30-
self.serial = cast(serial.Serial, self.bus.serialPortOrig)
79+
self.serial = cast(SerialMock, self.bus.serialPortOrig)
3180
self.serial.reset_input_buffer()
3281

3382
def tearDown(self):
3483
self.bus.shutdown()
3584

3685
def test_recv_extended(self):
37-
self.serial.write(b"T12ABCDEF2AA55\r")
86+
self.serial.set_input_buffer(b"T12ABCDEF2AA55\r")
3887
msg = self.bus.recv(TIMEOUT)
3988
self.assertIsNotNone(msg)
4089
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
@@ -44,7 +93,7 @@ def test_recv_extended(self):
4493
self.assertSequenceEqual(msg.data, [0xAA, 0x55])
4594

4695
# Ewert Energy Systems CANDapter specific
47-
self.serial.write(b"x12ABCDEF2AA55\r")
96+
self.serial.set_input_buffer(b"x12ABCDEF2AA55\r")
4897
msg = self.bus.recv(TIMEOUT)
4998
self.assertIsNotNone(msg)
5099
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
@@ -54,15 +103,19 @@ def test_recv_extended(self):
54103
self.assertSequenceEqual(msg.data, [0xAA, 0x55])
55104

56105
def test_send_extended(self):
106+
payload = b"T12ABCDEF2AA55\r"
57107
msg = can.Message(
58108
arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55]
59109
)
60110
self.bus.send(msg)
111+
self.assertEqual(payload, self.serial.get_output_buffer())
112+
113+
self.serial.set_input_buffer(payload)
61114
rx_msg = self.bus.recv(TIMEOUT)
62115
self.assertTrue(msg.equals(rx_msg, timestamp_delta=None))
63116

64117
def test_recv_standard(self):
65-
self.serial.write(b"t4563112233\r")
118+
self.serial.set_input_buffer(b"t4563112233\r")
66119
msg = self.bus.recv(TIMEOUT)
67120
self.assertIsNotNone(msg)
68121
self.assertEqual(msg.arbitration_id, 0x456)
@@ -72,15 +125,19 @@ def test_recv_standard(self):
72125
self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33])
73126

74127
def test_send_standard(self):
128+
payload = b"t4563112233\r"
75129
msg = can.Message(
76130
arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33]
77131
)
78132
self.bus.send(msg)
133+
self.assertEqual(payload, self.serial.get_output_buffer())
134+
135+
self.serial.set_input_buffer(payload)
79136
rx_msg = self.bus.recv(TIMEOUT)
80137
self.assertTrue(msg.equals(rx_msg, timestamp_delta=None))
81138

82139
def test_recv_standard_remote(self):
83-
self.serial.write(b"r1238\r")
140+
self.serial.set_input_buffer(b"r1238\r")
84141
msg = self.bus.recv(TIMEOUT)
85142
self.assertIsNotNone(msg)
86143
self.assertEqual(msg.arbitration_id, 0x123)
@@ -89,15 +146,19 @@ def test_recv_standard_remote(self):
89146
self.assertEqual(msg.dlc, 8)
90147

91148
def test_send_standard_remote(self):
149+
payload = b"r1238\r"
92150
msg = can.Message(
93151
arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8
94152
)
95153
self.bus.send(msg)
154+
self.assertEqual(payload, self.serial.get_output_buffer())
155+
156+
self.serial.set_input_buffer(payload)
96157
rx_msg = self.bus.recv(TIMEOUT)
97158
self.assertTrue(msg.equals(rx_msg, timestamp_delta=None))
98159

99160
def test_recv_extended_remote(self):
100-
self.serial.write(b"R12ABCDEF6\r")
161+
self.serial.set_input_buffer(b"R12ABCDEF6\r")
101162
msg = self.bus.recv(TIMEOUT)
102163
self.assertIsNotNone(msg)
103164
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
@@ -106,19 +167,23 @@ def test_recv_extended_remote(self):
106167
self.assertEqual(msg.dlc, 6)
107168

108169
def test_send_extended_remote(self):
170+
payload = b"R12ABCDEF6\r"
109171
msg = can.Message(
110172
arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6
111173
)
112174
self.bus.send(msg)
175+
self.assertEqual(payload, self.serial.get_output_buffer())
176+
177+
self.serial.set_input_buffer(payload)
113178
rx_msg = self.bus.recv(TIMEOUT)
114179
self.assertTrue(msg.equals(rx_msg, timestamp_delta=None))
115180

116181
def test_partial_recv(self):
117-
self.serial.write(b"T12ABCDEF")
182+
self.serial.set_input_buffer(b"T12ABCDEF")
118183
msg = self.bus.recv(TIMEOUT)
119184
self.assertIsNone(msg)
120185

121-
self.serial.write(b"2AA55\rT12")
186+
self.serial.set_input_buffer(b"2AA55\rT12")
122187
msg = self.bus.recv(TIMEOUT)
123188
self.assertIsNotNone(msg)
124189
self.assertEqual(msg.arbitration_id, 0x12ABCDEF)
@@ -130,28 +195,21 @@ def test_partial_recv(self):
130195
msg = self.bus.recv(TIMEOUT)
131196
self.assertIsNone(msg)
132197

133-
self.serial.write(b"ABCDEF2AA55\r")
198+
self.serial.set_input_buffer(b"ABCDEF2AA55\r")
134199
msg = self.bus.recv(TIMEOUT)
135200
self.assertIsNotNone(msg)
136201

137202
def test_version(self):
138-
self.serial.write(b"V1013\r")
139203
hw_ver, sw_ver = self.bus.get_version(0)
204+
self.assertEqual(b"V\r", self.serial.get_output_buffer())
140205
self.assertEqual(hw_ver, 10)
141206
self.assertEqual(sw_ver, 13)
142207

143-
hw_ver, sw_ver = self.bus.get_version(0)
144-
self.assertIsNone(hw_ver)
145-
self.assertIsNone(sw_ver)
146-
147208
def test_serial_number(self):
148-
self.serial.write(b"NA123\r")
149209
sn = self.bus.get_serial_number(0)
210+
self.assertEqual(b"N\r", self.serial.get_output_buffer())
150211
self.assertEqual(sn, "A123")
151212

152-
sn = self.bus.get_serial_number(0)
153-
self.assertIsNone(sn)
154-
155213

156214
if __name__ == "__main__":
157215
unittest.main()

0 commit comments

Comments
 (0)